flowbook-python 0.1.0__py3-none-any.whl → 0.1.2__py3-none-any.whl

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 (26) hide show
  1. flowbook/_version.py +1 -1
  2. flowbook/kernel/flowbook_kernel.py +102 -18
  3. flowbook/kernel/notebook_state.py +22 -0
  4. flowbook/kernel/protocol.py +8 -2
  5. flowbook/kernel/reproducibility_enforcer.py +25 -0
  6. flowbook/kernel/tests/test_meaningful_edit.py +243 -0
  7. flowbook/mcp/session.py +35 -11
  8. {flowbook_python-0.1.0.data → flowbook_python-0.1.2.data}/data/share/jupyter/labextensions/flowbook/package.json +2 -2
  9. {flowbook_python-0.1.0.data → flowbook_python-0.1.2.data}/data/share/jupyter/labextensions/flowbook/schemas/flowbook/package.json.orig +1 -1
  10. flowbook_python-0.1.0.data/data/share/jupyter/labextensions/flowbook/static/873.3ca7ae352f965bccc339.js → flowbook_python-0.1.2.data/data/share/jupyter/labextensions/flowbook/static/873.3edec525c5c79ec55cd6.js +1 -1
  11. flowbook_python-0.1.0.data/data/share/jupyter/labextensions/flowbook/static/remoteEntry.97e370ce33befeb5451b.js → flowbook_python-0.1.2.data/data/share/jupyter/labextensions/flowbook/static/remoteEntry.9c89c62fdbe6db112e2d.js +1 -1
  12. {flowbook_python-0.1.0.dist-info → flowbook_python-0.1.2.dist-info}/METADATA +5 -2
  13. {flowbook_python-0.1.0.dist-info → flowbook_python-0.1.2.dist-info}/RECORD +26 -25
  14. {flowbook_python-0.1.0.dist-info → flowbook_python-0.1.2.dist-info}/WHEEL +1 -1
  15. {flowbook_python-0.1.0.data → flowbook_python-0.1.2.data}/data/etc/jupyter/jupyter_server_config.d/flowbook.json +0 -0
  16. {flowbook_python-0.1.0.data → flowbook_python-0.1.2.data}/data/share/jupyter/labextensions/flowbook/install.json +0 -0
  17. {flowbook_python-0.1.0.data → flowbook_python-0.1.2.data}/data/share/jupyter/labextensions/flowbook/schemas/flowbook/plugin.json +0 -0
  18. {flowbook_python-0.1.0.data → flowbook_python-0.1.2.data}/data/share/jupyter/labextensions/flowbook/static/728.b1df4bca1a3305d0d0a7.js +0 -0
  19. {flowbook_python-0.1.0.data → flowbook_python-0.1.2.data}/data/share/jupyter/labextensions/flowbook/static/905.94c2bfb401597cc2a103.js +0 -0
  20. {flowbook_python-0.1.0.data → flowbook_python-0.1.2.data}/data/share/jupyter/labextensions/flowbook/static/905.94c2bfb401597cc2a103.js.LICENSE.txt +0 -0
  21. {flowbook_python-0.1.0.data → flowbook_python-0.1.2.data}/data/share/jupyter/labextensions/flowbook/static/951.ba84389925d6a0676e79.js +0 -0
  22. {flowbook_python-0.1.0.data → flowbook_python-0.1.2.data}/data/share/jupyter/labextensions/flowbook/static/style.js +0 -0
  23. {flowbook_python-0.1.0.data → flowbook_python-0.1.2.data}/data/share/jupyter/labextensions/flowbook/static/third-party-licenses.json +0 -0
  24. {flowbook_python-0.1.0.data → flowbook_python-0.1.2.data}/data/share/jupyter/nbi_extensions/flowbook/extension.json +0 -0
  25. {flowbook_python-0.1.0.dist-info → flowbook_python-0.1.2.dist-info}/entry_points.txt +0 -0
  26. {flowbook_python-0.1.0.dist-info → flowbook_python-0.1.2.dist-info}/licenses/LICENSE +0 -0
flowbook/_version.py CHANGED
@@ -1,4 +1,4 @@
1
1
  # This file is auto-generated by Hatchling. As such, do not:
2
2
  # - modify
3
3
  # - track in version control e.g. be sure to add to .gitignore
4
- __version__ = VERSION = '0.1.0'
4
+ __version__ = VERSION = '0.1.2'
@@ -342,6 +342,7 @@ See checkpoint.py sections 13-14 for implementation details.
342
342
  ================================================================================
343
343
  """
344
344
 
345
+ import ast
345
346
  import os
346
347
  import re
347
348
  import time
@@ -525,7 +526,7 @@ class FlowbookKernel(BaseFlowbookKernel, Magics):
525
526
  if msg_type == "notebook_structure":
526
527
  self._process_structure_update(msg["cell_order"])
527
528
  elif msg_type == "cell_edited":
528
- self._process_cell_edit(msg["cell_id"])
529
+ self._process_cell_edit(msg["cell_id"], msg.get("source"))
529
530
  elif msg_type == "continue_after_violation":
530
531
  self._set_continue_after_violation(msg["enabled"])
531
532
  elif msg_type == "sync":
@@ -579,26 +580,94 @@ class FlowbookKernel(BaseFlowbookKernel, Magics):
579
580
  log(f"[structure_update] ERROR: {e}")
580
581
  log(f"[structure_update] Traceback: {traceback.format_exc()}")
581
582
 
582
- def _process_cell_edit(self, cell_id: str) -> None:
583
- """Process a cell_edited command. Marks the cell stale ([Inst-Edit])."""
583
+ def _source_fingerprint(self, source: str) -> Optional[str]:
584
+ """Canonical AST fingerprint of a cell's source ([Inst-Edit]).
585
+
586
+ Runs the source through IPython's input transformer so magics/`!`
587
+ commands become valid Python, then returns ``ast.dump`` of the parsed
588
+ tree. This is insensitive to comments, blank lines, indentation, and
589
+ source positions, so cosmetic edits produce an identical fingerprint.
590
+ Returns None when the (possibly partial) source cannot be parsed.
591
+ """
592
+ try:
593
+ transformed = self.shell.input_transformer_manager.transform_cell(source)
594
+ return ast.dump(ast.parse(transformed))
595
+ except (SyntaxError, ValueError):
596
+ return None
597
+
598
+ def _send_cell_edit_metadata(self, cell_id: str, stale_cells: list, icon: str, text: str) -> None:
599
+ """Emit metadata + status reflecting a cell's post-edit staleness."""
600
+ staleness_reasons = self._enforcer._notebook_state.get_all_reasons()
601
+ metadata = ReproducibilityMetadata(
602
+ cell_id=cell_id,
603
+ execution_seq=self._enforcer.seq_counter,
604
+ read_locs=[],
605
+ write_locs=[],
606
+ changed_locs=[],
607
+ stale_cells=stale_cells,
608
+ cell_order=self._enforcer.cell_order,
609
+ staleness_reasons=staleness_reasons,
610
+ )
611
+ self._send_flowbook_message(build_metadata_message(metadata))
612
+ self._send_flowbook_message(build_status_message(icon, text, cell_id=cell_id))
613
+
614
+ def _process_cell_edit(self, cell_id: str, source: Optional[str] = None) -> None:
615
+ """Process a cell_edited command ([Inst-Edit]).
616
+
617
+ Compares the edited source's AST fingerprint against the one captured when
618
+ the cell last executed. If they match, the edit is cosmetic
619
+ (whitespace/comments) or a round-trip back to the last-run source, so
620
+ CODE_CHANGED is cleared (the cell returns to clean unless stale for another
621
+ reason). Otherwise — including unparseable or sourceless edits — the cell is
622
+ marked stale, preserving the conservative legacy behavior.
623
+ """
584
624
  if not cell_id:
585
625
  return
586
- stale_cells = self._enforcer.mark_cell_edited(cell_id)
587
- if cell_id in stale_cells:
588
- staleness_reasons = self._enforcer._notebook_state.get_all_reasons()
589
- metadata = ReproducibilityMetadata(
590
- cell_id=cell_id,
591
- execution_seq=self._enforcer.seq_counter,
592
- read_locs=[],
593
- write_locs=[],
594
- changed_locs=[],
595
- stale_cells=stale_cells,
596
- cell_order=self._enforcer.cell_order,
597
- staleness_reasons=staleness_reasons,
626
+
627
+ new_fp = self._source_fingerprint(source) if source is not None else None
628
+ old_fp = self._enforcer.get_fingerprint(cell_id)
629
+
630
+ def _fp_repr(fp: Optional[str]) -> str:
631
+ if fp is None:
632
+ return "None"
633
+ return f"<{len(fp)}c #{hash(fp) & 0xffffffff:08x}>"
634
+
635
+ log(
636
+ f"[Inst-Edit] cell={cell_id} source_present={source is not None} "
637
+ f"new_fp={_fp_repr(new_fp)} old_fp={_fp_repr(old_fp)} "
638
+ f"match={new_fp is not None and old_fp is not None and new_fp == old_fp}"
639
+ )
640
+
641
+ if new_fp is not None and old_fp is not None and new_fp == old_fp:
642
+ # Source semantically matches the last execution — not stale due to code.
643
+ stale_before = set(self._enforcer.get_stale_cells())
644
+ stale_cells = self._enforcer.clear_code_changed(cell_id)
645
+ changed = set(stale_cells) != stale_before
646
+ log(
647
+ f"[Inst-Edit] cell={cell_id} DECISION=cosmetic/revert -> clear_code_changed "
648
+ f"(status_changed={changed}, stale_now={stale_cells})"
598
649
  )
599
- self._send_flowbook_message(build_metadata_message(metadata))
600
- self._send_flowbook_message(
601
- build_status_message("✏️", "Cell edited, marked stale", cell_id=cell_id)
650
+ if changed:
651
+ self._send_cell_edit_metadata(
652
+ cell_id, stale_cells, "↩️", "Edit reverted to last run, cleared"
653
+ )
654
+ return
655
+
656
+ if source is not None and new_fp is None:
657
+ reason = "unparseable"
658
+ elif source is None:
659
+ reason = "no-source"
660
+ else:
661
+ reason = "ast-differs"
662
+ stale_cells = self._enforcer.mark_cell_edited(cell_id)
663
+ marked = cell_id in stale_cells
664
+ log(
665
+ f"[Inst-Edit] cell={cell_id} DECISION=meaningful ({reason}) -> mark_cell_edited "
666
+ f"(marked_stale={marked}, stale_now={stale_cells})"
667
+ )
668
+ if marked:
669
+ self._send_cell_edit_metadata(
670
+ cell_id, stale_cells, "✏️", "Cell edited, marked stale"
602
671
  )
603
672
 
604
673
  def _set_continue_after_violation(self, enabled: bool) -> None:
@@ -1263,6 +1332,10 @@ class FlowbookKernel(BaseFlowbookKernel, Magics):
1263
1332
  if cell_meta and "cell_order" in cell_meta:
1264
1333
  self._enforcer.set_cell_order(cell_meta["cell_order"])
1265
1334
 
1335
+ # Capture the raw source (before magic stripping) for the [Inst-Edit]
1336
+ # fingerprint, so it matches the source the frontend/MCP send on edit.
1337
+ original_code = code
1338
+
1266
1339
  # Check for notebook_structure magic (parse and remove if present)
1267
1340
  code = self._process_structure_magic(code)
1268
1341
 
@@ -1470,6 +1543,17 @@ class FlowbookKernel(BaseFlowbookKernel, Magics):
1470
1543
  error(f"Reproducibility truncation: {err.message}")
1471
1544
  self._send_truncation_details(err.detail["truncation_details"])
1472
1545
 
1546
+ # [Inst-Edit] baseline: record the fingerprint of the source that
1547
+ # just committed, so future edits can be classified as meaningful
1548
+ # or cosmetic. Reached only on the committed path (rejected
1549
+ # executions return above).
1550
+ _baseline_fp = self._source_fingerprint(original_code)
1551
+ self._enforcer.set_fingerprint(self._cell_id, _baseline_fp)
1552
+ log(
1553
+ f"[Inst-Edit] cell={self._cell_id} stored baseline fingerprint "
1554
+ f"{'None' if _baseline_fp is None else f'<{len(_baseline_fp)}c #{hash(_baseline_fp) & 0xffffffff:08x}>'}"
1555
+ )
1556
+
1473
1557
  # Display results (no longer skip on violations since we don't reject)
1474
1558
  skip_display = False
1475
1559
  if (
@@ -75,6 +75,11 @@ class NotebookState:
75
75
  execution_seq: Dict[str, int] = field(default_factory=dict) # cell_id -> seq number
76
76
  structural_reads_values: Dict[str, Dict[str, Dict[str, str]]] = field(default_factory=dict) # cell_id -> var -> attr -> value
77
77
  typed_changes: Dict[str, List["Change"]] = field(default_factory=dict) # cell_id -> changes
78
+ # Canonical AST fingerprint of the source last executed for each cell.
79
+ # Used by [Inst-Edit] to decide whether a source edit is meaningful: an edit
80
+ # whose fingerprint matches this baseline is treated as cosmetic (clears
81
+ # CODE_CHANGED) rather than marking the cell stale.
82
+ fingerprints: Dict[str, str] = field(default_factory=dict) # cell_id -> AST fingerprint
78
83
 
79
84
  # =========================================================================
80
85
  # Status Access
@@ -106,6 +111,21 @@ class NotebookState:
106
111
  """Get all reasons for a cell's staleness."""
107
112
  return self.get_status(cell_id).reasons
108
113
 
114
+ def set_fingerprint(self, cell_id: str, fingerprint: Optional[str]) -> None:
115
+ """Store the AST fingerprint of the source last executed for a cell.
116
+
117
+ Passing None (unparseable source) clears any stored fingerprint, which
118
+ makes subsequent edits fall through to the conservative mark-stale path.
119
+ """
120
+ if fingerprint is None:
121
+ self.fingerprints.pop(cell_id, None)
122
+ else:
123
+ self.fingerprints[cell_id] = fingerprint
124
+
125
+ def get_fingerprint(self, cell_id: str) -> Optional[str]:
126
+ """Return the AST fingerprint of the source last executed for a cell."""
127
+ return self.fingerprints.get(cell_id)
128
+
109
129
  def clear_pre_execution_reasons(self, cell_id: str) -> None:
110
130
  """Clear pre-execution reasons (NEVER_EXECUTED, CODE_CHANGED) from a cell.
111
131
 
@@ -487,6 +507,7 @@ class NotebookState:
487
507
  self.execution_seq.pop(deleted_cell, None)
488
508
  self.structural_reads_values.pop(deleted_cell, None)
489
509
  self.typed_changes.pop(deleted_cell, None)
510
+ self.fingerprints.pop(deleted_cell, None)
490
511
  if deleted_cell in self.cell_order:
491
512
  self.cell_order.remove(deleted_cell)
492
513
 
@@ -594,6 +615,7 @@ class NotebookState:
594
615
  self.execution_seq.clear()
595
616
  self.structural_reads_values.clear()
596
617
  self.typed_changes.clear()
618
+ self.fingerprints.clear()
597
619
 
598
620
  # =========================================================================
599
621
  # Debug/Inspection
@@ -113,11 +113,17 @@ def build_notebook_structure_message(cell_order: List[str]) -> dict:
113
113
  }
114
114
 
115
115
 
116
- def build_cell_edited_message(cell_id: str) -> dict:
117
- """Build a cell_edited message."""
116
+ def build_cell_edited_message(cell_id: str, source: Optional[str] = None) -> dict:
117
+ """Build a cell_edited message.
118
+
119
+ The optional source is the cell's current text; the kernel uses it to decide
120
+ whether the edit is meaningful (AST changed) or cosmetic (whitespace/comments).
121
+ Omitting it preserves the legacy always-mark-stale behavior.
122
+ """
118
123
  return {
119
124
  "type": CELL_EDITED,
120
125
  "cell_id": cell_id,
126
+ "source": source,
121
127
  }
122
128
 
123
129
 
@@ -2054,6 +2054,31 @@ class ReproducibilityEnforcer:
2054
2054
 
2055
2055
  return self.get_stale_cells()
2056
2056
 
2057
+ def set_fingerprint(self, cell_id: str, fingerprint: Optional[str]) -> None:
2058
+ """Store the AST fingerprint of the source last executed for a cell.
2059
+
2060
+ Used by [Inst-Edit] to distinguish meaningful edits from cosmetic ones
2061
+ (whitespace/comments). Computed by the kernel, which owns source access.
2062
+ """
2063
+ self._notebook_state.set_fingerprint(cell_id, fingerprint)
2064
+
2065
+ def get_fingerprint(self, cell_id: str) -> Optional[str]:
2066
+ """Return the AST fingerprint of the source last executed for a cell."""
2067
+ return self._notebook_state.get_fingerprint(cell_id)
2068
+
2069
+ def clear_code_changed(self, cell_id: str) -> List[str]:
2070
+ """Clear the CODE_CHANGED reason for a cell (symmetric [Inst-Edit]).
2071
+
2072
+ Called when an edit brings a cell's source back to an AST identical to
2073
+ what it last executed. Removes the CODE_CHANGED/NEVER_EXECUTED pre-execution
2074
+ reasons, marking the cell clean only if no other staleness reason remains
2075
+ (e.g. FORWARD_STALE from an upstream cell is preserved).
2076
+
2077
+ Returns current stale cells list.
2078
+ """
2079
+ self._notebook_state.clear_pre_execution_reasons(cell_id)
2080
+ return self.get_stale_cells()
2081
+
2057
2082
  def get_execution_records_size(self) -> int:
2058
2083
  """
2059
2084
  Calculate approximate memory size of execution records in bytes.
@@ -0,0 +1,243 @@
1
+ """
2
+ Tests for the meaningful-edit refinement of [Inst-Edit].
3
+
4
+ A source edit only marks a cell stale when it changes the cell's meaning. Edits
5
+ that touch only whitespace, blank lines, indentation, or comments are cosmetic
6
+ and leave the cell's status untouched; an edit that brings the source back to an
7
+ AST identical to the last execution clears the CODE_CHANGED reason.
8
+
9
+ Covers:
10
+ - FlowbookKernel._source_fingerprint (AST canonicalization, magics, syntax errors)
11
+ - NotebookState fingerprint storage (set/get/clear/delete)
12
+ - ReproducibilityEnforcer.clear_code_changed (symmetric clear)
13
+ - FlowbookKernel._process_cell_edit (end-to-end edit classification)
14
+ """
15
+
16
+ import pytest
17
+
18
+ from flowbook.kernel.flowbook_kernel import FlowbookKernel
19
+ from flowbook.kernel.notebook_state import NotebookState
20
+ from flowbook.kernel.models import Reason, ReasonType
21
+ from flowbook.kernel.tests.conftest import ReproducibilityTestHelper
22
+
23
+
24
+ def _make_kernel(enforcer=None):
25
+ """Build a FlowbookKernel shell with just enough wiring to test edits.
26
+
27
+ Uses __new__ to skip the heavy IPython kernel init, then attaches a real
28
+ IPython InteractiveShell (its input_transformer_manager makes magics
29
+ fingerprint correctly) and captures outgoing flowbook messages.
30
+ """
31
+ from IPython.core.interactiveshell import InteractiveShell
32
+
33
+ kernel = FlowbookKernel.__new__(FlowbookKernel)
34
+ kernel.shell = InteractiveShell.instance()
35
+
36
+ sent = []
37
+ kernel._send_flowbook_message = lambda msg: sent.append(msg)
38
+ kernel._sent_messages = sent
39
+ if enforcer is not None:
40
+ kernel._enforcer = enforcer
41
+ return kernel
42
+
43
+
44
+ # ---------------------------------------------------------------------------
45
+ # _source_fingerprint
46
+ # ---------------------------------------------------------------------------
47
+
48
+
49
+ class TestSourceFingerprint:
50
+ def setup_method(self):
51
+ self.kernel = _make_kernel()
52
+
53
+ def fp(self, src):
54
+ return self.kernel._source_fingerprint(src)
55
+
56
+ def test_comment_only_edit_is_equal(self):
57
+ assert self.fp("x = 1 # original") == self.fp("x = 1 # changed comment")
58
+
59
+ def test_added_comment_line_is_equal(self):
60
+ assert self.fp("x = 1") == self.fp("# leading note\nx = 1")
61
+
62
+ def test_whitespace_and_blank_lines_equal(self):
63
+ assert self.fp("x=1") == self.fp("x = 1 ")
64
+ assert self.fp("x = 1") == self.fp("\n\nx = 1\n\n")
65
+
66
+ def test_reindented_block_is_equal(self):
67
+ a = "def f():\n return 1"
68
+ b = "def f():\n return 1" # different indent width, same structure
69
+ assert self.fp(a) == self.fp(b)
70
+
71
+ def test_string_literal_change_differs(self):
72
+ assert self.fp('x = "hello"') != self.fp('x = "world"')
73
+
74
+ def test_real_code_change_differs(self):
75
+ assert self.fp("x = 1") != self.fp("x = 2")
76
+ assert self.fp("x = 1") != self.fp("y = 1")
77
+
78
+ def test_underscore_numeric_literal_is_equal(self):
79
+ assert self.fp("x = 1000") == self.fp("x = 1_000")
80
+
81
+ def test_int_vs_float_literal_differs(self):
82
+ assert self.fp("x = 1") != self.fp("x = 1.0")
83
+
84
+ def test_magics_are_parseable_and_stable(self):
85
+ a = "%timeout 5\nx = 1 # a"
86
+ b = "%timeout 5\nx = 1 # b"
87
+ assert self.fp(a) is not None
88
+ assert self.fp(a) == self.fp(b)
89
+
90
+ def test_shell_command_parseable(self):
91
+ assert self.fp("!ls -la") is not None
92
+
93
+ def test_syntax_error_returns_none(self):
94
+ assert self.fp("x = ") is None
95
+ assert self.fp("def f(:\n pass") is None
96
+
97
+
98
+ # ---------------------------------------------------------------------------
99
+ # NotebookState fingerprint storage
100
+ # ---------------------------------------------------------------------------
101
+
102
+
103
+ class TestNotebookStateFingerprints:
104
+ def test_set_and_get(self):
105
+ ns = NotebookState()
106
+ ns.set_fingerprint("a", "FP")
107
+ assert ns.get_fingerprint("a") == "FP"
108
+
109
+ def test_get_unknown_is_none(self):
110
+ ns = NotebookState()
111
+ assert ns.get_fingerprint("missing") is None
112
+
113
+ def test_set_none_clears(self):
114
+ ns = NotebookState()
115
+ ns.set_fingerprint("a", "FP")
116
+ ns.set_fingerprint("a", None)
117
+ assert ns.get_fingerprint("a") is None
118
+
119
+ def test_delete_pops_fingerprint(self):
120
+ ns = NotebookState()
121
+ ns.cell_order = ["a"]
122
+ ns.set_fingerprint("a", "FP")
123
+ ns.handle_delete("a")
124
+ assert ns.get_fingerprint("a") is None
125
+
126
+ def test_clear_drops_fingerprints(self):
127
+ ns = NotebookState()
128
+ ns.set_fingerprint("a", "FP")
129
+ ns.clear()
130
+ assert ns.get_fingerprint("a") is None
131
+
132
+
133
+ # ---------------------------------------------------------------------------
134
+ # Enforcer.clear_code_changed
135
+ # ---------------------------------------------------------------------------
136
+
137
+
138
+ class TestClearCodeChanged:
139
+ def test_clear_only_code_changed_goes_clean(self):
140
+ helper = ReproducibilityTestHelper()
141
+ helper.set_cell_order(["a"])
142
+ helper.execute_cell("a", {}, {"x": 1}, writes={"x"})
143
+ sdc = helper.sdc
144
+ sdc._notebook_state.set_stale("a", {Reason(ReasonType.CODE_CHANGED)})
145
+ assert "a" in sdc.get_stale_cells()
146
+
147
+ stale = sdc.clear_code_changed("a")
148
+ assert "a" not in stale
149
+ assert sdc._notebook_state.is_clean("a")
150
+
151
+ def test_clear_preserves_other_reason(self):
152
+ helper = ReproducibilityTestHelper()
153
+ helper.set_cell_order(["a"])
154
+ helper.execute_cell("a", {}, {"x": 1}, writes={"x"})
155
+ sdc = helper.sdc
156
+ sdc._notebook_state.set_stale(
157
+ "a",
158
+ {
159
+ Reason(ReasonType.CODE_CHANGED),
160
+ Reason(ReasonType.FORWARD_STALE, loc="x", cell_id="z"),
161
+ },
162
+ )
163
+
164
+ stale = sdc.clear_code_changed("a")
165
+ assert "a" in stale # FORWARD_STALE keeps it stale
166
+ reasons = {r.type for r in sdc._notebook_state.get_reasons("a")}
167
+ assert ReasonType.CODE_CHANGED not in reasons
168
+ assert ReasonType.FORWARD_STALE in reasons
169
+
170
+
171
+ # ---------------------------------------------------------------------------
172
+ # _process_cell_edit end-to-end
173
+ # ---------------------------------------------------------------------------
174
+
175
+
176
+ def _setup_executed_cell(source="x = 1"):
177
+ """Return (kernel, enforcer) with cell 'a' executed and fingerprinted."""
178
+ helper = ReproducibilityTestHelper()
179
+ helper.set_cell_order(["a"])
180
+ helper.execute_cell("a", {}, {"x": 1}, writes={"x"})
181
+ sdc = helper.sdc
182
+ kernel = _make_kernel(enforcer=sdc)
183
+ sdc.set_fingerprint("a", kernel._source_fingerprint(source))
184
+ return kernel, sdc
185
+
186
+
187
+ class TestProcessCellEdit:
188
+ def test_cosmetic_edit_stays_clean_no_message(self):
189
+ kernel, sdc = _setup_executed_cell("x = 1")
190
+ kernel._process_cell_edit("a", "x = 1 # just a comment")
191
+ assert sdc._notebook_state.is_clean("a")
192
+ assert kernel._sent_messages == []
193
+
194
+ def test_whitespace_edit_stays_clean(self):
195
+ kernel, sdc = _setup_executed_cell("x = 1")
196
+ kernel._process_cell_edit("a", "\n\nx=1\n")
197
+ assert sdc._notebook_state.is_clean("a")
198
+ assert kernel._sent_messages == []
199
+
200
+ def test_meaningful_edit_marks_stale_and_emits(self):
201
+ kernel, sdc = _setup_executed_cell("x = 1")
202
+ kernel._process_cell_edit("a", "x = 2")
203
+ assert "a" in sdc.get_stale_cells()
204
+ assert any(m.get("type") == "metadata" for m in kernel._sent_messages)
205
+
206
+ def test_syntax_error_marks_stale(self):
207
+ kernel, sdc = _setup_executed_cell("x = 1")
208
+ kernel._process_cell_edit("a", "x = ")
209
+ assert "a" in sdc.get_stale_cells()
210
+
211
+ def test_revert_to_last_run_clears_staleness(self):
212
+ kernel, sdc = _setup_executed_cell("x = 1")
213
+ # First, a meaningful edit marks it stale.
214
+ kernel._process_cell_edit("a", "x = 2")
215
+ assert "a" in sdc.get_stale_cells()
216
+ kernel._sent_messages.clear()
217
+ # Editing back to source with the last-run AST clears CODE_CHANGED.
218
+ kernel._process_cell_edit("a", "x = 1 # back to original")
219
+ assert sdc._notebook_state.is_clean("a")
220
+ assert any(m.get("type") == "metadata" for m in kernel._sent_messages)
221
+
222
+ def test_cosmetic_edit_preserves_other_staleness(self):
223
+ kernel, sdc = _setup_executed_cell("x = 1")
224
+ sdc._notebook_state.set_stale(
225
+ "a", {Reason(ReasonType.FORWARD_STALE, loc="x", cell_id="z")}
226
+ )
227
+ kernel._process_cell_edit("a", "x = 1 # comment only")
228
+ # Still stale for the upstream reason; no spurious status flash.
229
+ assert "a" in sdc.get_stale_cells()
230
+ reasons = {r.type for r in sdc._notebook_state.get_reasons("a")}
231
+ assert ReasonType.FORWARD_STALE in reasons
232
+ assert kernel._sent_messages == []
233
+
234
+ def test_unexecuted_cell_without_fingerprint_marks_stale(self):
235
+ # No record / no fingerprint -> falls through to conservative mark.
236
+ helper = ReproducibilityTestHelper()
237
+ helper.set_cell_order(["a"])
238
+ helper.execute_cell("a", {}, {"x": 1}, writes={"x"})
239
+ sdc = helper.sdc
240
+ kernel = _make_kernel(enforcer=sdc)
241
+ # No set_fingerprint -> get_fingerprint is None -> mark stale on any edit.
242
+ kernel._process_cell_edit("a", "x = 1 # comment")
243
+ assert "a" in sdc.get_stale_cells()
flowbook/mcp/session.py CHANGED
@@ -958,17 +958,41 @@ class NotebookSession:
958
958
  # Editing
959
959
  # ------------------------------------------------------------------
960
960
 
961
- def _mark_cell_edited(self, cell_id: str) -> None:
962
- """Mark a cell as stale and notify the kernel, if previously executed."""
963
- if cell_id in self.executed_cells:
961
+ def _mark_cell_edited(self, cell_id: str, new_source: Optional[str] = None) -> None:
962
+ """Notify the kernel of a source edit ([Inst-Edit]), if previously executed.
963
+
964
+ When ``new_source`` is provided, the kernel classifies the edit using the
965
+ cell's AST fingerprint (meaningful vs cosmetic) and we reconcile
966
+ ``_stale_cells`` from its authoritative reply. When omitted (e.g. the
967
+ refactoring tools, which always change semantics), the kernel marks the
968
+ cell stale and we mirror that optimistically — preserving legacy behavior.
969
+ """
970
+ if cell_id not in self.executed_cells:
971
+ return
972
+
973
+ result = KernelHelper.execute_code(
974
+ self.kernel_client,
975
+ "",
976
+ timeout=10,
977
+ store_history=False,
978
+ flowbook_msg={"type": "cell_edited", "cell_id": cell_id, "source": new_source},
979
+ )
980
+
981
+ if new_source is None:
982
+ # Legacy/refactor path: the edit is always meaningful.
964
983
  self._stale_cells.add(cell_id)
965
- KernelHelper.execute_code(
966
- self.kernel_client,
967
- "",
968
- timeout=10,
969
- store_history=False,
970
- flowbook_msg={"type": "cell_edited", "cell_id": cell_id},
971
- )
984
+ return
985
+
986
+ # Source provided: the kernel decides. It emits a metadata message only
987
+ # when the edit changes the cell's status; reconcile from that reply.
988
+ for fb in result.get("flowbook_messages", []):
989
+ if isinstance(fb, dict) and fb.get("type") == "metadata":
990
+ if cell_id in set(fb.get("stale_cells", [])):
991
+ self._stale_cells.add(cell_id)
992
+ else:
993
+ self._stale_cells.discard(cell_id)
994
+ return
995
+ # No metadata reply → cosmetic no-op; leave staleness unchanged.
972
996
 
973
997
  def edit_cell(self, cell_id: str, new_source: str) -> Dict[str, Any]:
974
998
  """Update a cell's source and mark it stale if previously executed."""
@@ -978,7 +1002,7 @@ class NotebookSession:
978
1002
  old_source = get_cell_source(cell)
979
1003
  set_cell_source(cell, new_source)
980
1004
 
981
- self._mark_cell_edited(cell_id)
1005
+ self._mark_cell_edited(cell_id, new_source)
982
1006
  self._put_contents_api()
983
1007
 
984
1008
  return {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flowbook",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Reproducibility enforcement for Jupyter notebooks: a JupyterLab extension that enforces rerun consistency by tracking variable- and column-level dependencies between cells.",
5
5
  "keywords": [
6
6
  "jupyter",
@@ -139,7 +139,7 @@
139
139
  "outputDir": "flowbook/labextension",
140
140
  "schemaDir": "schema",
141
141
  "_build": {
142
- "load": "static/remoteEntry.97e370ce33befeb5451b.js",
142
+ "load": "static/remoteEntry.9c89c62fdbe6db112e2d.js",
143
143
  "extension": "./extension",
144
144
  "style": "./style"
145
145
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flowbook",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Reproducibility enforcement for Jupyter notebooks: a JupyterLab extension that enforces rerun consistency by tracking variable- and column-level dependencies between cells.",
5
5
  "keywords": [
6
6
  "jupyter",
@@ -1 +1 @@
1
- "use strict";(self.webpackChunkflowbook=self.webpackChunkflowbook||[]).push([[873],{873(e,t,o){o.r(t),o.d(t,{default:()=>ee});var n=o(615),s=o(602);class l{constructor(e){this._kernelChanged=new s.Signal(this),this._notebookKernels=new Map,this._tracker=e,this._setupTracking()}get kernelChanged(){return this._kernelChanged}getKernelType(e){const t=e.sessionContext.session?.kernel?.name;return this._classifyKernel(t)}isFlowbookKernel(e){return"flowbook_kernel"===this.getKernelType(e)}_classifyKernel(e){return e?"flowbook_kernel"===e?"flowbook_kernel":"other":"none"}_setupTracking(){this._tracker.forEach(e=>{this._monitorNotebook(e)}),this._tracker.widgetAdded.connect((e,t)=>{this._monitorNotebook(t)})}_monitorNotebook(e){const t=e.context.path,o=this.getKernelType(e);this._notebookKernels.set(t,o),e.sessionContext.kernelChanged.connect(()=>{const o=this._notebookKernels.get(t)||"none",n=this.getKernelType(e);o!==n&&(this._notebookKernels.set(t,n),this._kernelChanged.emit({notebook:e,previousKernel:o,currentKernel:n}))}),e.disposed.connect(()=>{this._notebookKernels.delete(t)})}}var a=o(247),r=o(345),i=o(338);function c(e){const t=[],o=e.content.widgets;for(let e=0;e<o.length;e++)"code"===o[e].model.type&&t.push(o[e].model.id);return t}function d(e,t){if(e<0)throw new Error(`Index must be non-negative (got: ${e})`);let o;if(e<26)o="@"+String.fromCharCode("A".charCodeAt(0)+e);else if(e<702){const t=e-26;o="@"+String.fromCharCode("A".charCodeAt(0)+Math.floor(t/26))+String.fromCharCode("A".charCodeAt(0)+t%26)}else{if(!(e<18278))throw new Error(`Index ${e} is too large (max supported: 18277 for @ZZZ)`);{const t=e-702;o="@"+String.fromCharCode("A".charCodeAt(0)+Math.floor(t/676))+String.fromCharCode("A".charCodeAt(0)+Math.floor(t/26%26))+String.fromCharCode("A".charCodeAt(0)+t%26)}}return t?`${o} / ${t.slice(-4)}`:o}function h(e,t){const o=t.indexOf(e);if(-1===o)return e;try{return d(o,e)}catch(t){return e}}function u(e){return void 0!==e.var_name?e.var_name:"string"==typeof e.qualifier?e.qualifier:void 0}function m(e){const t=new Map,o={var:"Var",col:"Col",cols:"Cols",rows:"Rows",file:"File"};for(const n of e){const e=o[n.type]||n.type,s=u(n);if(s){let o=t.get(s);o||(o={types:new Map},t.set(s,o));let l=o.types.get(e);l||(l=[],o.types.set(e,l)),"col"===n.type&&l.push(n.name)}else{let o=t.get(n.name);o||(o={types:new Map},t.set(n.name,o)),o.types.has(e)||o.types.set(e,[])}}return t}function _(e){return 0===e.size?r.createElement("span",{className:"flowbook-none"}," None"):r.createElement("ul",{className:"flowbook-variable-list"},Array.from(e.entries()).map(([e,t])=>{if(!Array.from(t.types.values()).some(e=>e.length>0)){const o=Array.from(t.types.keys())[0],n=o&&"Var"!==o?` (${o})`:"";return r.createElement("li",{key:e},r.createElement("code",null,e,n&&r.createElement("span",{style:{color:"#888",fontSize:"0.9em"}},n)))}return r.createElement("li",{key:e},r.createElement("code",null,e),r.createElement("ul",{className:"flowbook-loc-sublist"},Array.from(t.types.entries()).map(([e,t])=>0===t.length?r.createElement("li",{key:e},r.createElement("span",{className:"flowbook-loc-type"},e)):r.createElement("li",{key:e},r.createElement("span",{className:"flowbook-loc-type"},e,":")," ",r.createElement("code",null,t.sort().join(", "))))))}))}const f=({metadata:e,cellId:t,currentCellOrder:o})=>{if(!e)return r.createElement("div",{className:"flowbook-metadata-empty"},r.createElement("p",null,"No reproducibility metadata available."),r.createElement("p",null,"Execute a cell to see dependency tracking."));const n=e.stale_cells.length>0,s=function(e){const t=new Map,o={var:"Var",col:"Col",cols:"Cols",rows:"Rows",file:"File"};for(const n of e){const e=o[n.type]||n.type,s=u(n);if(s){let o=t.get(s);o||(o={types:new Map},t.set(s,o));let l=o.types.get(e);l||(l=[],o.types.set(e,l)),"col"===n.type&&l.push(n.name)}else{let o=t.get(n.name);o||(o={types:new Map},t.set(n.name,o)),o.types.has(e)||o.types.set(e,[])}}return t}(e.read_locs||[]),l=m(e.write_locs||[]),a=m(e.changed_locs||[]),i=function(e,t){if(e.length!==t.length)return!1;for(let o=0;o<e.length;o++)if(e[o].type!==t[o].type||e[o].name!==t[o].name||e[o].qualifier!==t[o].qualifier)return!1;return!0}(e.write_locs||[],e.changed_locs||[]);return r.createElement("div",{className:"flowbook-metadata-content"},t&&r.createElement(r.Fragment,null,r.createElement("div",{className:"flowbook-metadata-header"},"Cell: ",h(t,o)),r.createElement("div",{className:"flowbook-metadata-section"},r.createElement("div",{className:"flowbook-metadata-item"},r.createElement("span",{style:{fontSize:"0.85em",color:"#666"}},"Id: "),r.createElement("code",{style:{fontSize:"0.85em",color:"#666"}},t))),r.createElement("div",{className:"flowbook-metadata-divider"})),r.createElement("div",{className:"flowbook-metadata-section"},r.createElement("div",{className:"flowbook-metadata-item"},r.createElement("strong",null,"Execution #:")," ",e.execution_seq)),e.errors&&e.errors.length>0&&r.createElement(r.Fragment,null,r.createElement("div",{className:"flowbook-metadata-divider"}),r.createElement("div",{className:"flowbook-metadata-section flowbook-error-section"},r.createElement("div",{className:"flowbook-metadata-item"},r.createElement("strong",{style:{color:"#d32f2f"}},"Errors:"),r.createElement("ul",{className:"flowbook-error-list",style:{margin:"4px 0",paddingLeft:"16px"}},e.errors.map((e,t)=>{return r.createElement("li",{key:t,style:{marginBottom:"6px"}},r.createElement("div",{style:{fontWeight:600,color:"#d32f2f",fontSize:"0.9em"}},{no_read_and_write:"Read And Write Same Location",write_before_read:"Undefined Variable",no_read_before_write:"Forward Contamination",no_write_after_read:"Backward Mutation",unrecoverable_mutation:"Unrecoverable Mutation"}[n=e.error_type]||n),r.createElement("div",{style:{fontSize:"0.85em",color:"#333"}},e.message),e.causer_cell&&r.createElement("div",{style:{fontSize:"0.85em",color:"#666"}},"Conflicts with:"," ",r.createElement("code",null,h(e.causer_cell,o))));var n}))))),(void 0!==e.execute_duration_ms||void 0!==e.code_duration_ms||void 0!==e.state_duration_ms||void 0!==e.check_duration_ms)&&r.createElement(r.Fragment,null,r.createElement("div",{className:"flowbook-metadata-divider"}),r.createElement("div",{className:"flowbook-metadata-section"},r.createElement("div",{className:"flowbook-metadata-item"},r.createElement("strong",null,"Timing:"),r.createElement("ul",{className:"flowbook-timing-list"},void 0!==e.execute_duration_ms&&r.createElement("li",null,"Execute:"," ",r.createElement("code",null,e.execute_duration_ms.toFixed(0)," ms")),void 0!==e.code_duration_ms&&r.createElement("li",null,"Code: ",r.createElement("code",null,e.code_duration_ms.toFixed(0)," ms")),void 0!==e.state_duration_ms&&r.createElement("li",null,"State:"," ",r.createElement("code",null,e.state_duration_ms.toFixed(0)," ms")),void 0!==e.check_duration_ms&&r.createElement("li",null,"Check:"," ",r.createElement("code",null,e.check_duration_ms.toFixed(0)," ms")))))),r.createElement("div",{className:"flowbook-metadata-divider"}),r.createElement("div",{className:"flowbook-metadata-section"},r.createElement("div",{className:"flowbook-metadata-item"},r.createElement("strong",null,"Reads:"),_(s))),r.createElement("div",{className:"flowbook-metadata-divider"}),r.createElement("div",{className:"flowbook-metadata-section"},r.createElement("div",{className:"flowbook-metadata-item"},r.createElement("strong",null,i?"Writes:":"Writes (Intended):"),_(l))),!i&&r.createElement(r.Fragment,null,r.createElement("div",{className:"flowbook-metadata-divider"}),r.createElement("div",{className:"flowbook-metadata-section"},r.createElement("div",{className:"flowbook-metadata-item"},r.createElement("strong",null,"Changed (Actual):"),_(a)))),n&&r.createElement(r.Fragment,null,r.createElement("div",{className:"flowbook-metadata-divider"}),r.createElement("div",{className:"flowbook-metadata-section flowbook-stale-section"},r.createElement("div",{className:"flowbook-metadata-item"},r.createElement("strong",null,"Stale Cells:"),r.createElement("ul",{className:"flowbook-cell-list flowbook-stale"},e.stale_cells.map((e,t)=>r.createElement("li",{key:t},r.createElement("code",null,h(e,o)))))))),e.structural_warnings&&e.structural_warnings.length>0&&r.createElement(r.Fragment,null,r.createElement("div",{className:"flowbook-metadata-divider"}),r.createElement("div",{className:"flowbook-metadata-section flowbook-warning-section"},r.createElement("div",{className:"flowbook-warning-header"},"Structural Warnings"),r.createElement("div",{className:"flowbook-warning-content"},r.createElement("ul",{className:"flowbook-warning-list"},e.structural_warnings.map((e,t)=>r.createElement("li",{key:t},e)))))))},p=({icon:e,text:t,cellRef:o})=>e||t?r.createElement("div",{className:"flowbook-status-header"},r.createElement("div",{style:{fontSize:"0.7em",color:"#888",textTransform:"uppercase",letterSpacing:"0.05em",marginBottom:"2px"}},"Last Execution"),r.createElement("div",{style:{fontSize:"0.85em",fontFamily:"var(--jp-code-font-family, monospace)"}},o&&r.createElement("strong",{style:{marginRight:"6px"}},o),e," ",t)):null;class g extends a.Widget{constructor(){super(),this._metadata=null,this._cellId=null,this._currentCellOrder=[],this._statusIcon=null,this._statusText=null,this._statusCellRef=null,this._root=null,this.id="flowbook-metadata-panel",this.addClass("flowbook-metadata-panel"),this.title.label="FlowBook",this.title.closable=!0,this.title.caption="FlowBook cell metadata",this.render()}render(){this._root||(this._root=(0,i.H)(this.node)),this._root.render(r.createElement(r.Fragment,null,r.createElement(p,{icon:this._statusIcon,text:this._statusText,cellRef:this._statusCellRef}),r.createElement(f,{metadata:this._metadata,cellId:this._cellId,currentCellOrder:this._currentCellOrder})))}updateMetadata(e,t,o){this._metadata=e,this._cellId=t,this._currentCellOrder=o,this.render()}updateStatus(e,t,o){this._statusIcon=e,this._statusText=t,this._statusCellRef=o?h(o,this._currentCellOrder):null,this.render()}clear(){this._metadata=null,this._cellId=null,this._currentCellOrder=[],this.render()}dispose(){this.isDisposed||(this._root?.unmount(),this._root=null,super.dispose())}}var k=o(395);function b(e){return void 0!==e.var_name?e.var_name:"string"==typeof e.qualifier?e.qualifier:void 0}function w(e,t){return"number"==typeof e.qualifier&&"number"==typeof t.qualifier?e.qualifier===t.qualifier:b(e)===b(t)}function y(e,t){switch(e.type){case"var":return"var"===t.type&&e.name===t.name;case"col":return"col"===t.type?w(e,t)&&e.name===t.name:"cols"===t.type&&w(e,t);case"cols":return("col"===t.type||"cols"===t.type)&&w(e,t);case"rows":return("col"===t.type||"rows"===t.type)&&w(e,t);case"file":return"file"===t.type&&e.name===t.name;default:return!1}}function v(e){const t=b(e);return t?`${t}.${e.name}`:e.name}function C(e){return e}o(740);const x={bulge:function({id:e,sourceX:t,sourceY:o,targetX:n,targetY:s,style:l,markerEnd:a,label:i,labelStyle:c,data:d}){const h=d?.bulge||50,u=Math.max(t,n)+h,m=o+.25*(s-o),_=o+.75*(s-o),f=`M ${t},${o} C ${u},${m} ${u},${_} ${n},${s}`,p=.125*t+.375*u+.375*u+.125*n,g=.125*o+.375*m+.375*_+.125*s,b=i?String(i):"",w=6.5*b.length;return r.createElement(r.Fragment,null,r.createElement(k.BaseEdge,{id:e,path:f,style:l,markerEnd:a}),b&&r.createElement("g",{transform:`translate(${p}, ${g})`},r.createElement("rect",{x:-w/2-4,y:-11,width:w+8,height:22,rx:3,ry:3,fill:"white",fillOpacity:.95,stroke:"var(--jp-border-color2, #ddd)",strokeWidth:.5}),r.createElement("text",{style:c,textAnchor:"middle",dominantBaseline:"central",className:"react-flow__edge-text"},b)))}},E={cell:function({data:e}){return r.createElement("div",{className:e.className},r.createElement(k.Handle,{type:"target",position:k.Position.Top,id:"center-top",style:{opacity:0}}),r.createElement(k.Handle,{type:"source",position:k.Position.Bottom,id:"center-bottom",style:{opacity:0}}),r.createElement(k.Handle,{type:"source",position:k.Position.Right,id:"right-source",style:{opacity:0}}),r.createElement(k.Handle,{type:"target",position:k.Position.Right,id:"right-target",style:{opacity:0}}),e.label)}},N={type:k.MarkerType.ArrowClosed,color:"#999",width:12,height:12},S={type:k.MarkerType.ArrowClosed,color:"#2472c8",width:14,height:14},M={type:k.MarkerType.ArrowClosed,color:"#dc3545",width:14,height:14};const $=({cellData:e})=>{const[t,o]=r.useState(null),{nodes:n,allEdges:s}=r.useMemo(()=>function(e){return{nodes:e.map((e,t)=>{let o="flowbook-dep-node";return e.hasError?o+=" flowbook-dep-node-error":e.isStale?o+=" flowbook-dep-node-stale":e.isExecuted||(o+=" flowbook-dep-node-unexecuted"),{id:e.cellId,type:"cell",position:{x:40,y:80*t},data:{label:e.label,className:o}}}),allEdges:function(e){const t=[];for(let o=0;o<e.length;o++)if(e[o].isExecuted)for(let n=o+1;n<e.length;n++){if(!e[n].isExecuted)continue;const s=n-o,l=[],a=new Set;for(const t of e[o].writeLocs)for(const o of e[n].readLocs)if(y(t,o)){const e=v(o);a.has(e)||(a.add(e),l.push(e))}l.length>0&&t.push({sourceId:e[o].cellId,targetId:e[n].cellId,kind:"data_dependency",locations:l,span:s,lane:0})}const o=new Map;for(let t=0;t<e.length;t++)o.set(e[t].cellId,t);const n=new Set;for(const s of e)for(const e of s.violations){if(!e.causer_cell)continue;const l=e.causer_cell.startsWith("@")?e.causer_cell.slice(1):e.causer_cell,a=o.get(s.cellId),r=o.get(l);if(void 0===a||void 0===r)continue;let i,c;"no_write_after_read"===e.error_type?(i=s.cellId,c=l):"no_read_before_write"===e.error_type?(i=l,c=s.cellId):(i=s.cellId,c=l);const d=`${i}-${c}-violation`;if(n.has(d))continue;n.add(d);const h=o.get(i)??0,u=o.get(c)??0,m=Math.abs(h-u);t.push({sourceId:i,targetId:c,kind:"violation",locations:e.locations,span:m||1,lane:0})}t.sort((e,t)=>e.span-t.span);for(let e=0;e<t.length;e++)t[e].lane=e+1;const s=[];for(let t=0;t<e.length-1;t++)s.push({sourceId:e[t].cellId,targetId:e[t+1].cellId,kind:"program_order",locations:[],span:1,lane:0});return s.concat(t)}(e).map(e=>{const t=`${e.sourceId}-${e.targetId}-${e.kind}`;if("program_order"===e.kind)return{id:t,source:e.sourceId,target:e.targetId,sourceHandle:"center-bottom",targetHandle:"center-top",type:"straight",style:{stroke:"#999",strokeWidth:1},markerEnd:N,data:{edgeKind:"program_order"}};const o=40+25*e.lane,n="violation"===e.kind;return{id:t,source:e.sourceId,target:e.targetId,sourceHandle:"right-source",targetHandle:"right-target",type:"bulge",data:{bulge:o,edgeKind:e.kind},label:(s=e.locations,0===s.length?"":s.length<=3?s.join(", "):s.slice(0,3).join(", ")+", ..."),labelStyle:{fontSize:10,fontFamily:"monospace",fill:n?"#dc3545":void 0},style:n?{stroke:"#dc3545",strokeWidth:2,strokeDasharray:"5,5"}:{stroke:"#2472c8",strokeWidth:2},markerEnd:n?M:S};var s})}}(e),[e]),l=r.useMemo(()=>s.filter(e=>{const o=e.data?.edgeKind;return"program_order"===o||"violation"===o||null!==t&&(e.source===t||e.target===t)}),[s,t]),a=r.useCallback((e,t)=>{o(t.id)},[]),i=r.useCallback(()=>{o(null)},[]);return r.createElement(k.ReactFlow,{nodes:n,edges:l,nodeTypes:E,edgeTypes:x,onNodeMouseEnter:a,onNodeMouseLeave:i,nodesDraggable:!1,nodesConnectable:!1,elementsSelectable:!1,fitView:!0,fitViewOptions:{padding:.15,minZoom:.5,maxZoom:1.2},minZoom:.3,maxZoom:2},r.createElement(k.Controls,{showInteractive:!1}))},I=({cellData:e})=>0===e.length?r.createElement("div",{className:"flowbook-metadata-empty"},r.createElement("p",null,"No cells to display."),r.createElement("p",null,"Execute cells to see dependencies.")):r.createElement("div",{className:"flowbook-dependencies-graph",style:{width:"100%",height:"100%",position:"relative"}},r.createElement("div",{style:{position:"absolute",top:0,left:0,right:0,bottom:0}},r.createElement(k.ReactFlowProvider,null,r.createElement($,{cellData:e}))));class A extends a.Widget{constructor(){super(),this._cellData=[],this._root=null,this.id="flowbook-dependencies-panel",this.addClass("flowbook-dependencies-panel"),this.title.label="Dependencies",this.title.closable=!0,this.title.caption="Cell dependency graph",this.render()}render(){this._root||(this._root=(0,i.H)(this.node)),this._root.render(r.createElement(I,{cellData:this._cellData}))}updateGraph(e){this._cellData=e,this.render()}clear(){this._cellData=[],this.render()}dispose(){this.isDisposed||(this._root?.unmount(),this._root=null,super.dispose())}}class R{constructor(e){this._staleCells=new Set,this._stalenessReasons=new Map,this._stalenessChanged=new s.Signal(this),this._notebook=e,this._setupKernelRestartListener()}get stalenessChanged(){return this._stalenessChanged}get staleCells(){return this._staleCells}isCellStale(e){return this._staleCells.has(e)}setReasons(e,t){this._stalenessReasons.set(e,t)}getReasons(e){return this._stalenessReasons.get(e)||[]}setReason(e,t){this._stalenessReasons.set(e,[t])}getReason(e){const t=this._stalenessReasons.get(e);return t&&t.length>0?t[0]:void 0}updateFromMetadata(e){const t=new Set(this._staleCells);this._staleCells=new Set(e.stale_cells);const o=new Set(e.stale_cells),n=[...o].filter(e=>!t.has(e)),s=[...t].filter(e=>!o.has(e));for(const e of s)this._stalenessReasons.delete(e);let l=!1;if(e.staleness_reasons)for(const[t,o]of Object.entries(e.staleness_reasons)){const e=this._stalenessReasons.get(t);JSON.stringify(e||[])!==JSON.stringify(o)&&(l=!0),this._stalenessReasons.set(t,o)}(n.length>0||s.length>0||l)&&this._stalenessChanged.emit({added:n,removed:s,current:[...this._staleCells]})}clear(){const e=[...this._staleCells];this._staleCells.clear(),this._stalenessReasons.clear(),e.length>0&&this._stalenessChanged.emit({added:[],removed:e,current:[]})}_setupKernelRestartListener(){this._notebook.sessionContext.statusChanged.connect((e,t)=>{"restarting"!==t&&"autorestarting"!==t||this.clear()})}dispose(){this._staleCells.clear(),this._stalenessReasons.clear()}}function O(e){return"message"in e}class W{updateStalenessNotice(e,t,o,n){if("code"!==e.model.type)return;const s=e.model.outputs,l=e.model.getMetadata("flowbook"),a=l?.errors&&l.errors.length>0;let r=!1;for(let e=0;e<s.length;e++){const t=C(s.get(e).toJSON());if(!0===t.metadata?.flowbook_violation_notice){r=!0;break}}if(a||r)return void this._removeNoticesByKey(s,"flowbook_staleness_notice");const i=s.length>0&&!0===s.get(0).toJSON().metadata?.flowbook_staleness_notice;if(t){const t=o.getReason(e.model.id)||{type:"unknown",message:"Dependencies changed"};if("never_executed"===t.type)return void(i&&this._removeNoticesByKey(s,"flowbook_staleness_notice"));const l=this.formatStalenessMessage(t,n,e.model.id),a=l.replace(/`([^`]+)`/g,"<code>$1</code>"),r="writer_conflict"===t.type?"Unresolved Violation":"",c=r?`⚠️ ${r}: ${l}`:`⚠️ ${l}`,d={output_type:"display_data",data:{"text/html":r?`<div class="flowbook-staleness-notice">⚠️ <b>${r}</b>: ${a} </div>`:`<div class="flowbook-staleness-notice">⚠️ ${a} </div>`,"text/plain":c},metadata:{flowbook_staleness_notice:!0}};if(i){const e=s.get(0).toJSON().data?.["text/plain"];if(e===c)return}const h=[d];for(let e=0;e<s.length;e++){const t=s.get(e).toJSON();C(t).metadata?.flowbook_staleness_notice||h.push(t)}s.fromJSON(h)}else i&&this._removeNoticesByKey(s,"flowbook_staleness_notice")}formatStalenessMessage(e,t,o){return O(e)?this._formatFrontendReason(e,t,o):this._formatBackendReason(e,t,o)}updateStalenessMetadata(e,t){const o=e.model.getMetadata("flowbook_staleness");if(!o)return;if(!O(o)||!o.causing_cell)return;const n=this.formatStalenessMessage(o,t,e.model.id);n!==o.message&&e.model.setMetadata("flowbook_staleness",{...o,message:n})}_formatFrontendReason(e,t,o){if("source_edited"===e.type)return"Source code was edited";if(!e.causing_cell)return e.message;const n=t.indexOf(e.causing_cell),s=t.indexOf(o),l=n<0,a=l?"a deleted cell":d(n),r=!l&&s>=0&&n<s?" above":l?"":" below",i=[];if(e.variables)for(const t of e.variables)i.push("`"+t+"`");if(e.columns)for(const[t,o]of Object.entries(e.columns))for(const e of o)i.push("`"+t+"."+e+"`");return"writer_conflict"===e.type&&i.length>0?`Writes ${i.join(", ")} already read by ${a}${r}`:i.length>0?`${i.join(", ")} modified by ${a}${r}`:"unknown"===e.type?`Dependencies modified by ${a}`:e.message}_formatBackendReason(e,t,o){const n="cell_id"in e?e.cell_id:void 0,s="loc"in e?e.loc:void 0,l=t.indexOf(o);let a="",r="",i=!1;if(n){const e=t.indexOf(n);i=e<0,a=i?"a deleted cell":d(e),!i&&l>=0&&(r=e<l?" above":" below")}switch(e.type){case"never_executed":return"Cell has never been executed";case"code_changed":return"Source code was edited";case"forward_stale":return s&&a?`\`${s}\` modified by ${a}${r}`:a?`Input modified by ${a}${r}`:"Input was modified";case"write_overlap":return s&&a?`\`${s}\` also written by ${a}`:a?`Writes conflict with ${a}`:"Write conflict detected";case"backward_stale":return s&&a?`\`${s}\` write conflict with ${a}`:"Write conflict detected";case"no_read_before_write":return s&&a?`Reads \`${s}\` written by ${a} ${r}`:"Reads value written by another cell";case"order_changed":return"Cell order changed";case"no_write_after_read":return s&&a?`Writes \`${s}\` already read by ${a} ${r}`:a?`Writes variable already read by ${a} ${r}`:"Writes variable already read by another cell";default:return"Cell is stale"}}_removeNoticesByKey(e,t){const o=[];let n=!1;for(let s=0;s<e.length;s++){const l=e.get(s).toJSON();C(l).metadata?.[t]?n=!0:o.push(l)}n&&e.fromJSON(o)}}class F{updateViolationNotice(e,t){if("code"!==e.model.type)return!1;const o=e.model.outputs,n=e.model.getMetadata("flowbook"),s=n?.errors;let l=!1,a="";for(let e=0;e<o.length;e++){const t=C(o.get(e).toJSON());if(!0===t.metadata?.flowbook_violation_notice){l=!0,a=t.data?.["text/plain"]||"";break}}if(s&&s.length>0){const{noticeOutput:e,plainText:n}=this._buildViolationNotice(s,t);if(l&&a===n)return!0;const r=[e];for(let e=0;e<o.length;e++){const t=o.get(e).toJSON(),n=C(t),s=!0===n.metadata?.flowbook_violation_notice,l=!0===n.metadata?.flowbook_staleness_notice,a="error"===t.output_type&&("ReproducibilityError"===n.ename||"ReproducibilityViolation"===n.ename),i="display_data"===t.output_type&&n.metadata?.predicate_violation;s||l||a||i||r.push(t)}return o.fromJSON(r),!0}if(l){const e=[];for(let t=0;t<o.length;t++){const n=o.get(t).toJSON(),s=C(n);s.metadata?.flowbook_violation_notice||e.push(n)}o.fromJSON(e)}return!1}_buildViolationNotice(e,t){const o=new Map;for(const n of e){let e=null;if(n.causer_cell&&"string"==typeof n.causer_cell){const o=n.causer_cell.startsWith("@")?n.causer_cell.slice(1):n.causer_cell,s=t.indexOf(o);e=s>=0?d(s):"a deleted cell"}const s=[...n.locations].sort().join(","),l=`${n.error_type}:${s}`;o.has(l)||o.set(l,{errorType:n.error_type,locs:n.locations,causers:[]}),e&&!o.get(l).causers.includes(e)&&o.get(l).causers.push(e)}const n=[],s=[];for(const e of o.values()){const t=e.locs.map(e=>"`"+e+"`").join(", ").replace(/`([^`]+)`/g,"<code>$1</code>"),o=e.causers.join(", ");let l;switch(e.errorType){case"no_write_after_read":l=o?`Writes ${t} already read by ${o}`:`Writes ${t} already read by cell above`;for(const t of e.locs)if(t.includes(".")){const[e,o]=t.split(".");l+=`<br>Use <code>${e}["${o}"]</code> = ... for full-column assignment`}break;case"no_read_before_write":l=o?`Reads ${t} written by ${o} below`:`Reads ${t} written by cell below`;break;case"no_read_and_write":l=`Reads and writes ${t}`;break;case"write_before_read":l=`${t} not defined by any cell above`;break;case"unrecoverable_mutation":l=`${t} was modified in place, which violates rerun consistency`;break;default:l=`Violation on ${t}`}n.push(l),s.push(l.replace(/<code>([^<]+)<\/code>/g,"`$1`"))}const l=n.map(e=>`<div>❌ ${e}</div>`).join(""),a=s.map(e=>`❌ ${e}`).join("\n");return{noticeOutput:{output_type:"display_data",data:{"text/html":`<div class="flowbook-error-notice">${l}</div>`,"text/plain":a},metadata:{flowbook_violation_notice:!0,flowbook_predicate_accepted:e[0].accepted,flowbook_violation_count:e.length}},plainText:a}}}class L{constructor(e,t){this._dependenciesPanel=null,this._depPanelFrameId=null,this._stalenessManagers=new Map,this._pendingRestartUpdate=new Set,this._executedInSession=new Map,this._monitoredNotebooks=new Set,this._stalenessNotice=new W,this._violationNotice=new F,this._isDisposed=!1,this._tracker=e,this._panel=t,this._initialize()}setDependenciesPanel(e){this._dependenciesPanel=e}refreshDependencies(){null!==this._depPanelFrameId&&cancelAnimationFrame(this._depPanelFrameId),this._depPanelFrameId=requestAnimationFrame(()=>{this._depPanelFrameId=null;const e=this._tracker.currentWidget;if(e){const t=this.getStalenessManager(e),o=c(e);this._updateDependenciesPanel(e,t,o)}})}updateStatus(e,t,o){this._panel.updateStatus(e,t,o)}getStalenessManager(e){const t=e.context.path;let o=this._stalenessManagers.get(t);return o||(o=new R(e),this._stalenessManagers.set(t,o),o.stalenessChanged.connect(()=>{this._updateAllCells(e)}),e.disposed.connect(()=>{o?.dispose(),this._stalenessManagers.delete(t),this._monitoredNotebooks.delete(t)})),o}updateCell(e,t,o,n){const s=e.model.id,l=t.isCellStale(s),a=this._executedInSession.get(n),r=void 0!==e.model.getMetadata("flowbook"),i=!(a&&a.has(s)||r),c=e.model.sharedModel.getSource(),d=!c||""===c.trim();if(e.node.classList.remove("flowbook-cell-stale"),e.node.classList.remove("flowbook-cell-unexecuted"),e.node.classList.remove("flowbook-cell-error"),d?this._stalenessNotice.updateStalenessNotice(e,!1,t,o):l?(e.node.classList.add("flowbook-cell-stale"),this._stalenessNotice.updateStalenessNotice(e,!0,t,o)):i?(e.node.classList.add("flowbook-cell-unexecuted"),this._stalenessNotice.updateStalenessNotice(e,!1,t,o)):this._stalenessNotice.updateStalenessNotice(e,!1,t,o),this._violationNotice.updateViolationNotice(e,o)&&e.node.classList.add("flowbook-cell-error"),this._stalenessNotice.updateStalenessMetadata(e,o),this._tracker.activeCell===e){const t=e.model.getMetadata("flowbook"),n=this._tracker.currentWidget;t&&n&&this._panel.updateMetadata(t,s,o)}}dispose(){if(!this._isDisposed){this._isDisposed=!0,this._tracker.currentChanged.disconnect(this._onNotebookChanged,this),this._tracker.activeCellChanged.disconnect(this._onActiveCellChanged,this),n.NotebookActions.executed.disconnect(this._onExecuted,this),null!==this._depPanelFrameId&&cancelAnimationFrame(this._depPanelFrameId);for(const e of this._stalenessManagers.values())e.dispose();this._stalenessManagers.clear(),this._monitoredNotebooks.clear(),this._executedInSession.clear()}}_initialize(){this._tracker.currentChanged.connect(this._onNotebookChanged,this),this._tracker.activeCellChanged.connect(this._onActiveCellChanged,this),n.NotebookActions.executed.connect(this._onExecuted,this),this._tracker.currentWidget&&this._monitorNotebook(this._tracker.currentWidget)}_onNotebookChanged(e,t){t&&this._monitorNotebook(t)}_onActiveCellChanged(e,t){const o=e.currentWidget;if(o)if(t&&"code"===t.model.type){const e=t.model.getMetadata("flowbook"),n=t.model.id,s=c(o);e?this._panel.updateMetadata(e,n,s):this._panel.clear()}else this._panel.clear();else this._panel.clear()}_onExecuted(e,t){const o=this._tracker.currentWidget;if(!o||o.content!==t.notebook)return;const n=o.context.path;let s=this._executedInSession.get(n);s||(s=new Set,this._executedInSession.set(n,s)),s.add(t.cell.model.id)}_monitorNotebook(e){const t=e.context.path;this._monitoredNotebooks.has(t)?this._updateAllCells(e):(this._monitoredNotebooks.add(t),this._updateAllCells(e),e.content.model?.cells.changed.connect(()=>{this._updateAllCells(e),this._updatePanelWithCurrentCellOrder(e)}),e.sessionContext.statusChanged.connect((o,n)=>{"restarting"===n||"autorestarting"===n?(this._pendingRestartUpdate.add(t),this._executedInSession.delete(t),this._clearAllFlowbookMetadata(e)):"idle"===n&&this._pendingRestartUpdate.has(t)&&(this._pendingRestartUpdate.delete(t),this._updateAllCells(e))}))}_updatePanelWithCurrentCellOrder(e){const t=this._tracker.activeCell;if(!t||"code"!==t.model.type)return;const o=t.model.getMetadata("flowbook");if(o){const n=t.model.id,s=c(e);this._panel.updateMetadata(o,n,s)}}_updateAllCells(e){const t=this.getStalenessManager(e),o=c(e),n=e.content.widgets,s=e.context.path;n.forEach(e=>{"code"===e.model.type&&this.updateCell(e,t,o,s)}),this.refreshDependencies()}_updateDependenciesPanel(e,t,o){if(!this._dependenciesPanel)return;const n=[],s=e.content.widgets;for(let e=0;e<s.length;e++){const l=s[e];if("code"!==l.model.type)continue;const a=l.model.id,r=o.indexOf(a);if(r<0)continue;const i=l.model.getMetadata("flowbook"),c=i?.errors||[];let h;try{h=d(r)}catch{h=a}n.push({cellId:a,index:r,label:h,readLocs:i?.read_locs||[],writeLocs:i?.write_locs||[],isStale:t.isCellStale(a),isExecuted:void 0!==i,hasError:c.length>0,violations:c})}n.sort((e,t)=>e.index-t.index),this._dependenciesPanel.updateGraph(n)}_clearAllFlowbookMetadata(e){this.getStalenessManager(e).clear(),e.content.widgets.forEach(e=>{if("code"!==e.model.type)return;e.model.deleteMetadata("flowbook"),e.model.deleteMetadata("flowbook_staleness");const t=e.model.outputs,o=[];for(let e=0;e<t.length;e++){const n=t.get(e).toJSON(),s=C(n);s.metadata?.flowbook_staleness_notice||s.metadata?.flowbook_violation_notice||o.push(n)}o.length!==t.length&&t.fromJSON(o),e.node.classList.remove("flowbook-stale-cell"),e.node.classList.remove("flowbook-unexecuted-cell"),e.node.classList.remove("flowbook-cell-stale"),e.node.classList.remove("flowbook-cell-unexecuted"),e.node.classList.remove("flowbook-cell-error")})}}class D{constructor(e,t){this._editTimers=new Map,this._executedCells=new Set,this._attachedKernel=null,this._listenedCellIds=new Set,this._comm=null,this._isDisposed=!1,this._pendingViolations=[],this._tracker=e,this._highlighter=t,this._setupHooks()}dispose(){if(!this._isDisposed){this._isDisposed=!0,n.NotebookActions.executed.disconnect(this._onCellExecuted,this),n.NotebookActions.executionScheduled.disconnect(this._onExecutionScheduled,this),this._tracker.currentChanged.disconnect(this._setupCellEditListener,this),this._tracker.currentChanged.disconnect(this._setupComm,this);for(const e of this._editTimers.values())clearTimeout(e);if(this._editTimers.clear(),this._comm){try{this._comm.close()}catch{}this._comm=null}this._attachedKernel=null}}sendCommand(e){this._comm?this._comm.send(e):console.warn("ReproducibilityExecutionHook: No comm channel, cannot send command:",e)}_setupHooks(){n.NotebookActions.executed.connect(this._onCellExecuted,this),n.NotebookActions.executionScheduled.connect(this._onExecutionScheduled,this),this._tracker.currentChanged.connect(this._setupCellEditListener,this),this._tracker.currentChanged.connect(this._setupComm,this),this._tracker.currentWidget&&(this._setupCellEditListener(),this._setupComm())}_setupCellEditListener(){const e=this._tracker.currentWidget;if(!e)return;const t=e.content;for(let e=0;e<t.widgets.length;e++)this._attachCellEditListener(t.widgets[e]);t.model?.cells.changed.connect((o,n)=>{for(let e=0;e<t.widgets.length;e++)this._attachCellEditListener(t.widgets[e]);"add"!==n.type&&"remove"!==n.type||this._sendNotebookStructure(e)})}_sendNotebookStructure(e){const t=c(e);t.length>0&&this.sendCommand({type:"notebook_structure",cell_order:t})}_attachCellEditListener(e){if("code"!==e.model.type)return;const t=e.model.id;this._listenedCellIds.has(t)||(this._listenedCellIds.add(t),e.model.sharedModel.changed.connect((e,o)=>{o.sourceChange&&this._onCellContentChanged(t)}))}_onCellContentChanged(e){if(!this._executedCells.has(e))return;const t=this._editTimers.get(e);t&&clearTimeout(t);const o=setTimeout(()=>{this._sendCellEdited(e),this._editTimers.delete(e)},1e3);this._editTimers.set(e,o)}_sendCellEdited(e){this.sendCommand({type:"cell_edited",cell_id:e})}_setupComm(){const e=this._tracker.currentWidget;e&&(this._connectComm(e),e.sessionContext.kernelChanged.connect(()=>{this._attachedKernel=null,this._connectComm(e)}),e.sessionContext.statusChanged.connect((t,o)=>{"restarting"===o?(this._attachedKernel=null,this._comm=null):"idle"===o&&null===this._comm&&this._connectComm(e)}))}_connectComm(e){const t=e.sessionContext.session?.kernel;t&&t!==this._attachedKernel&&(this._attachedKernel=t,this._comm=t.createComm("flowbook"),this._comm.onMsg=this._onCommMessage.bind(this),this._comm.open())}_onCommMessage(e){const t=e.content.data;if(!t||!t.type)return;const o=this._tracker.currentWidget;if(o)switch(t.type){case"metadata":{const{type:e,...n}=t,s=n;if(s.cell_id){const e=this._findCell(o,s.cell_id);if(e){e.model.setMetadata("flowbook",s);const t=this._getCurrentCellOrder(o),n=this._highlighter.getStalenessManager(o);this._highlighter.updateCell(e,n,t,o.context.path),this._highlighter.refreshDependencies()}}this._processMetadataUpdate(o,s);break}case"violation":{const{type:e,...o}=t,n=o;this._pendingViolations.push(n);break}case"status":this._highlighter.updateStatus(t.icon,t.text,t.cell_id)}}_onExecutionScheduled(e,t){const{notebook:o,cell:n}=t,s=this._tracker.currentWidget;if(!s||s.content!==o)return;const l=n.model.id,a=this._editTimers.get(l);a&&(clearTimeout(a),this._editTimers.delete(l));const r=c(s);r.length>0&&this.sendCommand({type:"notebook_structure",cell_order:r})}_onCellExecuted(e,t){const{notebook:o,cell:n}=t;if("code"!==n.model.type)return;this._executedCells.add(n.model.id);const s=this._tracker.currentWidget;if(!s||s.content!==o)return;const l=n.model.id;this._pendingViolations=this._pendingViolations.filter(e=>e.cell_id!==l);const a=this._getCurrentCellOrder(s),r=this._highlighter.getStalenessManager(s);this._highlighter.updateCell(n,r,a,s.context.path),this._highlighter.refreshDependencies()}_getCurrentCellOrder(e){return c(e)}_processMetadataUpdate(e,t){const o=this._highlighter.getStalenessManager(e);try{const n=new Set(o.staleCells),s=new Set(t.stale_cells),l=this._getCurrentCellOrder(e),a=[...s].filter(e=>!n.has(e));for(const n of a){const s=this._findCell(e,n);if(s){const e=s.model.sharedModel.getSource();if(!e||""===e.trim())continue}const a=t.staleness_reasons?.[n];let r;r=a&&a.length>0?this._backendReasonToFrontend(a[0],l):this._computeStalenessReason(e,n,t,l),o.setReason(n,r),s&&s.model.setMetadata("flowbook_staleness",r)}const r=[...n].filter(e=>!s.has(e));for(const t of r){const o=this._findCell(e,t);o&&o.model.deleteMetadata("flowbook_staleness")}}catch(e){console.error("ReproducibilityExecutionHook: Error computing staleness reasons:",e)}o.updateFromMetadata(t)}_backendReasonToFrontend(e,t){const o=e.cell_id,n=e.loc;let s="";if(o){const e=t.indexOf(o);s=e>=0?d(e):o}switch(e.type){case"never_executed":return{type:"unknown",message:"Cell has never been executed"};case"code_changed":return{type:"source_edited",message:"Source code was edited"};case"forward_stale":return n&&s?{type:"variable_modified",causing_cell:o,variables:[n],message:`\`${n}\` was modified by ${s}`}:{type:"variable_modified",causing_cell:o,message:s?`Input modified by ${s}`:"Input was modified"};case"write_overlap":return n&&s?{type:"writer_conflict",causing_cell:o,variables:[n],message:`Write overlap: \`${n}\` also written by ${s}`}:{type:"writer_conflict",causing_cell:o,message:s?`Write overlap with ${s}`:"Write overlap detected"};case"backward_stale":return n&&s?{type:"writer_conflict",causing_cell:o,variables:[n],message:`Write conflict on \`${n}\` with ${s}`}:{type:"writer_conflict",causing_cell:o,message:"Write conflict detected"};case"no_read_before_write":return n&&s?{type:"unknown",causing_cell:o,message:`Reads \`${n}\` from later cell ${s} (forward contamination)`}:{type:"unknown",causing_cell:o,message:"Reads from a later cell"};case"order_changed":return{type:"unknown",message:"Cell order changed"};case"no_write_after_read":return n&&s?{type:"variable_modified",causing_cell:o,variables:[n],message:`Wrote \`${n}\` read by earlier cell ${s} (backward mutation)`}:{type:"unknown",causing_cell:o,message:s?`Wrote to variable read by ${s}`:"Backward mutation detected"};default:return{type:"unknown",causing_cell:o,message:s?`Dependencies changed by ${s}`:"Cell is stale"}}}_computeStalenessReason(e,t,o,n){const s=o.cell_id;if(t===s&&(!o.changed_locs||0===o.changed_locs.length))return{type:"source_edited",causing_cell:s,message:"Source code was edited"};const l=this._findCell(e,t),a=l?.model.metadata,r=a?.flowbook,i=n.indexOf(s),c=i>=0?d(i):s,h=function(e,t){const o=[];for(const n of t)for(const t of e)if(y(t,n)){o.push(n);break}return o}(o.changed_locs||[],r?.read_locs||[]);if(h.length>0){const e=h.map(e=>"`"+v(e)+"`");return{type:"variable_modified",causing_cell:s,variables:h.map(e=>v(e)),message:`${e.join(", ")} modified by ${c}`}}const u=r?.write_locs||[],m=o.read_locs||[],_=[],f=new Set;for(const e of m){const t=`${e.type}:${e.qualifier||""}:${e.name}`;if(!f.has(t))for(const o of u)if(y(o,e)){_.push(v(e)),f.add(t);break}}if(_.length>0){const e=_.map(e=>"`"+e+"`");return{type:"writer_conflict",causing_cell:s,variables:_,message:`Writes ${e.join(", ")}, which was read by ${c}`}}return{type:"unknown",causing_cell:s,message:`Dependencies changed by ${c}`}}_findCell(e,t){const o=e.content.widgets;for(let e=0;e<o.length;e++)if(o[e].model.id===t)return o[e];return null}}class K{constructor(){this._overlays=new Map,this._observers=new Map,this._notebooks=new Map,this._listeners=new Map,this._domListeners=new Map}startMonitoring(e,t){this._overlays.set(e,new Map),this._notebooks.set(e,t),this.updateAllOverlays(e,t);const o=new MutationObserver(o=>{for(const n of o)if("childList"===n.type&&n.addedNodes.length>0)for(const o of n.addedNodes)if(o instanceof HTMLElement&&(o.classList.contains("jp-InputArea-editor")||o.querySelector(".jp-InputArea-editor")))return void this.updateAllOverlays(e,t)});o.observe(t.content.node,{childList:!0,subtree:!0}),this._observers.set(e,o);const n=[],s=()=>{this.updateAllOverlays(e,t)};t.content.model?.cells.changed.connect(s),n.push({signal:t.content.model?.cells.changed,callback:s}),this._listeners.set(e,n);const l=t.content.node,a=()=>{requestAnimationFrame(()=>{this.updateAllOverlays(e,t)})},r=[];for(const e of["cut","paste"])l.addEventListener(e,a,!0),r.push({event:e,handler:a});const i=e=>{const t=e;(t.metaKey||t.ctrlKey)&&"z"===t.key&&a()};l.addEventListener("keydown",i,!0),r.push({event:"keydown",handler:i}),this._domListeners.set(e,{node:l,handlers:r})}stopMonitoring(e){const t=this._observers.get(e);t&&(t.disconnect(),this._observers.delete(e));const o=this._overlays.get(e);o&&(o.forEach(e=>{e.remove()}),this._overlays.delete(e)),this._notebooks.delete(e);const n=this._listeners.get(e);n&&(n.forEach(({signal:e,callback:t})=>{try{e?.disconnect(t)}catch(e){}}),this._listeners.delete(e));const s=this._domListeners.get(e);if(s){for(const{event:e,handler:t}of s.handlers)s.node.removeEventListener(e,t,!0);this._domListeners.delete(e)}}updateAllOverlays(e,t){const o=this._overlays.get(e);if(!o)return;const n=new Map;let s=0;t.content.widgets.forEach(e=>{"code"===e.model.type&&(n.set(e.model.id,s),s++)}),t.content.widgets.forEach(e=>{if("code"===e.model.type){const t=e.model.id,s=n.get(t);if(void 0===s)return;const l=o.get(t);if(l&&l.parentNode)return void(l.textContent=d(s));const a=e.node.querySelector(".jp-InputArea-editor");if(a){l&&l.remove();const e=this.createOverlay(s);a.style.position="relative",a.appendChild(e),o.set(t,e)}}})}createOverlay(e){const t=document.createElement("div");return t.className="flowbook-cell-index-overlay",t.textContent=d(e),t}}var T=o(537),P=o(651);class H{constructor(e){this._highlighter=null,this._kernelDetector=e}setHighlighter(e){this._highlighter=e}createNew(e,t){const o=new T.ToolbarButton({icon:P.stepIntoIcon,tooltip:"Run all stale and unrun cells",onClick:async()=>{await this._runAllActionable(e)}});o.node.style.display="none",e.toolbar.insertItem(10,"flowbook-run-next-stale",o);const n=()=>{const t=this._kernelDetector.isFlowbookKernel(e);o.node.style.display=t?"":"none"};return e.sessionContext.ready.then(()=>{n()}),e.sessionContext.kernelChanged.connect(()=>{n()}),{dispose:()=>{o.dispose()},get isDisposed(){return o.isDisposed}}}async _runAllActionable(e){const t=e.content;for(let o=0;o<500;o++){let o=new Set;this._highlighter&&(o=this._highlighter.getStalenessManager(e).staleCells);let s=-1;const l=t.widgets;for(let e=0;e<l.length;e++){const t=l[e];if("code"!==t.model.type)continue;const n=t.model,a=n.sharedModel.getSource();if(!a||""===a.trim())continue;const r=t.model.id;if(o.has(r)||null===n.executionCount){s=e;break}}if(s<0)break;t.activeCellIndex=s,t.scrollToCell(l[s]),await n.NotebookActions.run(t,e.sessionContext);const a=l[s],r=a.model.outputs;let i=!1;if(r)for(let e=0;e<r.length;e++){const t=r.get(e);if(t&&"error"===t.type){i=!0;break}}if(i)break;const c=a.model.getMetadata("flowbook");if(c?.errors&&c.errors.length>0)break}}}var j=o(697);function q(e,t){const o=e.content.widgets;let n=0;for(let e=0;e<o.length;e++)if("code"===o[e].model.type){if(n===t)return e;n++}throw new Error(`Code cell index ${t} out of range (have ${n} code cells)`)}function B(e){const t=e.model?.outputs;if(!t)return"";const o=[];for(let e=0;e<t.length;e++){const n=t.get(e);if(!n)continue;const s=n.type;if("stream"===s)o.push(n.data?.["text/plain"]||n.text||"");else if("execute_result"===s||"display_data"===s){const e=n.data?.["text/plain"];e&&o.push(e)}else if("error"===s){const e=n.traceback;if(e){const t=String.fromCharCode(27),n=new RegExp(t+"\\[[0-9;]*m","g");o.push(e.join("\n").replace(n,""))}else o.push(`${n.ename}: ${n.evalue}`)}}return o.join("\n")}function J(e,t){const o=e.content.widgets[t];if(!o||"code"!==o.model.type)return!1;const n=o.model.outputs;if(!n)return!1;for(let e=0;e<n.length;e++){const t=n.get(e);if(t&&"error"===t.type)return!0}return!1}let V=null,z=null,U=null,Z=null;function G(e,t,o,n){V=e,z=t,U=o,Z=n}function X(){const e=Z?.currentWidget;if(!e)throw new Error("No active notebook");return e}function Y(){if(!V)throw new Error("FlowBook not active");return V.getStalenessManager(X())}class Q{constructor(e,t,o,n){this._panel=null,this._dependenciesPanel=null,this._highlighter=null,this._executionHook=null,this._isActive=!1,this._activeNotebookPath=null,this._statusListenerNotebook=null,this._app=e,this._tracker=t,this._kernelDetector=o,this._cellIndexManager=new K,this._toolbarExtension=n,this._setupKernelChangeListener(),this._checkCurrentNotebook()}_setupKernelChangeListener(){this._kernelDetector.kernelChanged.connect((e,t)=>{"flowbook_kernel"===t.currentKernel?this._activate():"flowbook_kernel"===t.previousKernel&&this._deactivate()}),this._tracker.currentChanged.connect(()=>{this._checkCurrentNotebook()})}_checkCurrentNotebook(){const e=this._tracker.currentWidget;e&&(e.sessionContext.ready.then(()=>{this._kernelDetector.isFlowbookKernel(e)?this._activate():this._deactivate()}),this._statusListenerNotebook&&this._statusListenerNotebook!==e&&this._statusListenerNotebook.sessionContext.statusChanged.disconnect(this._onStatusChanged,this),this._statusListenerNotebook!==e&&(e.sessionContext.statusChanged.connect(this._onStatusChanged,this),this._statusListenerNotebook=e))}_onStatusChanged(){const e=this._statusListenerNotebook;if(!e)return;const t=this._kernelDetector.isFlowbookKernel(e);t&&!this._isActive?this._activate():t&&this._isActive?this._writeKernelDiscovery(e):!t&&this._isActive&&this._deactivate()}_activate(){if(this._isActive)return;this._panel=new g,this._app.shell.add(this._panel,"right",{rank:510}),this._dependenciesPanel=new A,this._app.shell.add(this._dependenciesPanel,"right",{rank:520}),this._highlighter=new L(this._tracker,this._panel),this._highlighter.setDependenciesPanel(this._dependenciesPanel),this._toolbarExtension.setHighlighter(this._highlighter),this._executionHook=new D(this._tracker,this._highlighter),this._highlighter.refreshDependencies(),G(this._highlighter,this._executionHook,this._kernelDetector,this._tracker);const e=this._tracker.currentWidget;e&&(this._activeNotebookPath=e.context.path,this._cellIndexManager.startMonitoring(this._activeNotebookPath,e),this._syncInitialState(e),this._writeKernelDiscovery(e)),this._isActive=!0}_syncInitialState(e){if(!this._executionHook)return;const t=c(e);0!==t.length&&(this._executionHook.sendCommand({type:"notebook_structure",cell_order:t}),this._executionHook.sendCommand({type:"sync"}))}_writeKernelDiscovery(e){const t=e.sessionContext.session;if(!t||!t.kernel)return;const o=e.context.path,n=t.kernel.id;(async function(e="",t={}){const o=j.ServerConnection.makeSettings(),n=`${o.baseUrl}flowbook/${e}`;let s;try{s=await j.ServerConnection.makeRequest(n,t,o)}catch(e){throw new j.ServerConnection.NetworkError(e)}let l=await s.text();if(l.length>0)try{l=JSON.parse(l)}catch{}if(!s.ok){const e=l.error||l.message||l;throw new j.ServerConnection.ResponseError(s,e)}return l})(`kernel-discovery/${encodeURIComponent(o)}`,{method:"PUT",body:JSON.stringify({kernel_name:t.kernel.name,connection_file:`kernel-${n}.json`,pid:0})}).catch(()=>{})}_deactivate(){this._isActive&&(this._activeNotebookPath&&(this._cellIndexManager.stopMonitoring(this._activeNotebookPath),this._activeNotebookPath=null),this._panel&&(this._panel.dispose(),this._panel=null),this._dependenciesPanel&&(this._dependenciesPanel.dispose(),this._dependenciesPanel=null),this._highlighter&&(this._highlighter.dispose(),this._highlighter=null),this._executionHook&&(this._executionHook.dispose(),this._executionHook=null),G(null,null,null,null),this._isActive=!1)}}const ee=[{id:"flowbook:plugin",autoStart:!0,requires:[n.INotebookTracker],activate:(e,t)=>{window.app=e;const o=new l(t);!function(e,t,o){e.commands.addCommand("flowbook:exec-restore",{label:"Run with upstream state",isEnabled:()=>{try{const e=t.currentWidget;if(!e)return!1;if(!o.isFlowbookKernel(e))return!1;const n=e.content.activeCell;if(!n||"code"!==n.model.type)return!1;const s=n.model.getMetadata("flowbook"),l=s?.errors;return void 0!==l&&l.some(e=>"no_read_before_write"===e.error_type)}catch(e){return console.error("FlowBook exec-restore isEnabled error:",e),!1}},isVisible:()=>{try{const e=t.currentWidget;return!!e&&o.isFlowbookKernel(e)}catch{return!1}},execute:async()=>{const o=t.currentWidget;if(!o)return;const n=o.content.activeCell;if(!n||"code"!==n.model.type)return;const s=n.model.id,l=o.sessionContext.session;if(!l||!l.kernel)return;const a=l.kernel.requestExecute({code:`%exec_restore ${s}`,silent:!0,store_history:!1});await a.done,await e.commands.execute("notebook:run-cell")}})}(e,t,o),function(e,t,o){Z=t,U=o,e.commands.addCommand("flowbook:is-active",{execute:()=>{const e=t.currentWidget;if(!e||!U)return{active:!1};const o=U.isFlowbookKernel(e);return{active:o,kernel:o?"flowbook_kernel":e.sessionContext.session?.kernel?.name||"unknown"}}}),e.commands.addCommand("flowbook:get-metadata",{execute:e=>{const t=X(),o=e.cellIndex,n=q(t,o),s=t.content.widgets[n],l=s.model.getMetadata("flowbook");return{label:d(o),cell_id:s.model.id,...l||{},stale_cells_labels:l?.stale_cells?.map(e=>{const o=c(t).indexOf(e);return o>=0?d(o):e})}}}),e.commands.addCommand("flowbook:get-stale-cells",{execute:()=>{const e=X(),t=Y(),o=t.staleCells,n=c(e),s=[];return n.forEach((e,n)=>{if(o.has(e)){const o=t.getReason(e),l=o&&"message"in o?o.message:o?.type||"stale";s.push({index:n,label:d(n),cell_id:e,reason:l})}}),s}}),e.commands.addCommand("flowbook:get-next-actionable",{execute:()=>{const e=X(),t=e.content.widgets,o=V?Y().staleCells:new Set;let n=0,s=null,l=null;for(let a=0;a<t.length;a++){const r=t[a];if("code"!==r.model.type)continue;const i=r.model,c=i.sharedModel.getSource(),h=r.model.id,u=n;if(n++,!c||""===c.trim())continue;const m=r.model.getMetadata("flowbook");if(m?.errors&&m.errors.length>0)return{index:u,label:d(u),cell_id:h,reason:"error",error:m.errors[0].message};if(J(e,a))return{index:u,label:d(u),cell_id:h,reason:"error"};!s&&o.has(h)&&(s={index:u,label:d(u),cell_id:h,reason:"stale"}),l||null!==i.executionCount||(l={index:u,label:d(u),cell_id:h,reason:"unexecuted"})}return s||l||{done:!0}}}),e.commands.addCommand("flowbook:get-status",{execute:()=>{const e=X(),t=e.content.widgets,o=V?Y().staleCells:new Set;let n=0,s=0,l=0,a=0,r=0;for(let i=0;i<t.length;i++){const c=t[i];if("code"!==c.model.type)continue;n++;const d=c.model,h=d.sharedModel.getSource();if(!h||""===h.trim()){r++;continue}null!==d.executionCount&&s++,o.has(c.model.id)&&l++;const u=c.model.getMetadata("flowbook");u?.errors&&u.errors.length>0&&a++,J(e,i)&&a++}const i=n-r;return{total_code_cells:n,non_empty:i,executed:s,stale:l,clean:s-l,with_errors:a,reproducible:i>0&&s===i&&0===l&&0===a}}}),e.commands.addCommand("flowbook:get-cell-count",{execute:()=>{const e=X().content.widgets;let t=0,o=0;for(let n=0;n<e.length;n++)"code"===e[n].model.type?t++:"markdown"===e[n].model.type&&o++;return{total:e.length,code_cells:t,markdown_cells:o}}}),e.commands.addCommand("flowbook:get-cell",{execute:e=>{const t=X(),o=e.cellIndex,n=q(t,o),s=t.content.widgets[n],l=d(o),a=s.model,r=s.model.getMetadata("flowbook");return{label:l,cell_id:s.model.id,cell_type:s.model.type,source:a.sharedModel.getSource(),execution_count:a.executionCount,outputs_text:B(s),flowbook_meta:r||null}}}),e.commands.addCommand("flowbook:get-cell-output",{execute:e=>{const t=X(),o=e.cellIndex,n=q(t,o),s=t.content.widgets[n];return{label:d(o),outputs_text:B(s)}}}),e.commands.addCommand("flowbook:edit-cell-source",{execute:e=>{const t=X(),o=e.cellIndex,n=e.source,s=q(t,o),l=t.content.widgets[s];return l.model.sharedModel.setSource(n),{label:d(o),cell_id:l.model.id}}}),e.commands.addCommand("flowbook:move-cell",{execute:e=>{const t=X(),o=e.fromIndex,n=e.toIndex,s=q(t,o),l=q(t,n),a=t.content.model?.sharedModel;if(!a)throw new Error("No shared model");return a.moveCell(s,l),{label:d(o),newIndex:n}}}),e.commands.addCommand("flowbook:notify-structure",{execute:()=>{const e=X();if(z){const t=c(e);z.sendCommand({type:"notebook_structure",cell_order:t})}}}),e.commands.addCommand("flowbook:set-continue-after-violation",{execute:e=>{const t=e.enabled;return z&&z.sendCommand({type:"continue_after_violation",enabled:t}),{enabled:t}}}),e.commands.addCommand("flowbook:run-cell",{execute:async e=>{const t=X(),o=e.cellIndex,s=q(t,o),l=d(o);t.content.activeCellIndex=s,await n.NotebookActions.run(t.content,t.sessionContext);const a=t.content.widgets[s],r=a.model.getMetadata("flowbook"),i=J(t,s),c=function(e,t){const o=e.content.widgets[t];if(!o||"code"!==o.model.type)return[];const n=o.model.getMetadata("flowbook");return n?.errors||[]}(t,s);return{label:l,cell_id:a.model.id,status:i?"error":c.length>0?"violation":"ok",outputs_text:B(a),flowbook_meta:r||null,errors:c}}}),e.commands.addCommand("flowbook:run-actionable-cells",{execute:async()=>{X();const t=[];let o=0;for(;o<500;)try{const n=await e.commands.execute("flowbook:get-next-actionable");if(n.done)break;const s=n.index,l=n.label,a=await e.commands.execute("flowbook:run-cell",{cellIndex:s});if(o++,t.push({label:l,status:a.status,outputs_preview:(a.outputs_text||"").slice(0,200)}),"error"===a.status)break;if("violation"===a.status)break}catch(e){console.error("Error in run-actionable-cells:",e);break}return{results:t,cells_run:o,summary:await e.commands.execute("flowbook:get-status")}}})}(e,t,o);const s=new H(o);e.docRegistry.addWidgetExtension("Notebook",s),new Q(e,t,o,s)}}]}}]);
1
+ "use strict";(self.webpackChunkflowbook=self.webpackChunkflowbook||[]).push([[873],{873(e,t,o){o.r(t),o.d(t,{default:()=>ee});var n=o(830),s=o(602);class l{constructor(e){this._kernelChanged=new s.Signal(this),this._notebookKernels=new Map,this._tracker=e,this._setupTracking()}get kernelChanged(){return this._kernelChanged}getKernelType(e){const t=e.sessionContext.session?.kernel?.name;return this._classifyKernel(t)}isFlowbookKernel(e){return"flowbook_kernel"===this.getKernelType(e)}_classifyKernel(e){return e?"flowbook_kernel"===e?"flowbook_kernel":"other":"none"}_setupTracking(){this._tracker.forEach(e=>{this._monitorNotebook(e)}),this._tracker.widgetAdded.connect((e,t)=>{this._monitorNotebook(t)})}_monitorNotebook(e){const t=e.context.path,o=this.getKernelType(e);this._notebookKernels.set(t,o),e.sessionContext.kernelChanged.connect(()=>{const o=this._notebookKernels.get(t)||"none",n=this.getKernelType(e);o!==n&&(this._notebookKernels.set(t,n),this._kernelChanged.emit({notebook:e,previousKernel:o,currentKernel:n}))}),e.disposed.connect(()=>{this._notebookKernels.delete(t)})}}var a=o(247),r=o(345),i=o(338);function c(e){const t=[],o=e.content.widgets;for(let e=0;e<o.length;e++)"code"===o[e].model.type&&t.push(o[e].model.id);return t}function d(e,t){if(e<0)throw new Error(`Index must be non-negative (got: ${e})`);let o;if(e<26)o="@"+String.fromCharCode("A".charCodeAt(0)+e);else if(e<702){const t=e-26;o="@"+String.fromCharCode("A".charCodeAt(0)+Math.floor(t/26))+String.fromCharCode("A".charCodeAt(0)+t%26)}else{if(!(e<18278))throw new Error(`Index ${e} is too large (max supported: 18277 for @ZZZ)`);{const t=e-702;o="@"+String.fromCharCode("A".charCodeAt(0)+Math.floor(t/676))+String.fromCharCode("A".charCodeAt(0)+Math.floor(t/26%26))+String.fromCharCode("A".charCodeAt(0)+t%26)}}return t?`${o} / ${t.slice(-4)}`:o}function h(e,t){const o=t.indexOf(e);if(-1===o)return e;try{return d(o,e)}catch(t){return e}}function u(e){return void 0!==e.var_name?e.var_name:"string"==typeof e.qualifier?e.qualifier:void 0}function m(e){const t=new Map,o={var:"Var",col:"Col",cols:"Cols",rows:"Rows",file:"File"};for(const n of e){const e=o[n.type]||n.type,s=u(n);if(s){let o=t.get(s);o||(o={types:new Map},t.set(s,o));let l=o.types.get(e);l||(l=[],o.types.set(e,l)),"col"===n.type&&l.push(n.name)}else{let o=t.get(n.name);o||(o={types:new Map},t.set(n.name,o)),o.types.has(e)||o.types.set(e,[])}}return t}function _(e){return 0===e.size?r.createElement("span",{className:"flowbook-none"}," None"):r.createElement("ul",{className:"flowbook-variable-list"},Array.from(e.entries()).map(([e,t])=>{if(!Array.from(t.types.values()).some(e=>e.length>0)){const o=Array.from(t.types.keys())[0],n=o&&"Var"!==o?` (${o})`:"";return r.createElement("li",{key:e},r.createElement("code",null,e,n&&r.createElement("span",{style:{color:"#888",fontSize:"0.9em"}},n)))}return r.createElement("li",{key:e},r.createElement("code",null,e),r.createElement("ul",{className:"flowbook-loc-sublist"},Array.from(t.types.entries()).map(([e,t])=>0===t.length?r.createElement("li",{key:e},r.createElement("span",{className:"flowbook-loc-type"},e)):r.createElement("li",{key:e},r.createElement("span",{className:"flowbook-loc-type"},e,":")," ",r.createElement("code",null,t.sort().join(", "))))))}))}const f=({metadata:e,cellId:t,currentCellOrder:o})=>{if(!e)return r.createElement("div",{className:"flowbook-metadata-empty"},r.createElement("p",null,"No reproducibility metadata available."),r.createElement("p",null,"Execute a cell to see dependency tracking."));const n=e.stale_cells.length>0,s=function(e){const t=new Map,o={var:"Var",col:"Col",cols:"Cols",rows:"Rows",file:"File"};for(const n of e){const e=o[n.type]||n.type,s=u(n);if(s){let o=t.get(s);o||(o={types:new Map},t.set(s,o));let l=o.types.get(e);l||(l=[],o.types.set(e,l)),"col"===n.type&&l.push(n.name)}else{let o=t.get(n.name);o||(o={types:new Map},t.set(n.name,o)),o.types.has(e)||o.types.set(e,[])}}return t}(e.read_locs||[]),l=m(e.write_locs||[]),a=m(e.changed_locs||[]),i=function(e,t){if(e.length!==t.length)return!1;for(let o=0;o<e.length;o++)if(e[o].type!==t[o].type||e[o].name!==t[o].name||e[o].qualifier!==t[o].qualifier)return!1;return!0}(e.write_locs||[],e.changed_locs||[]);return r.createElement("div",{className:"flowbook-metadata-content"},t&&r.createElement(r.Fragment,null,r.createElement("div",{className:"flowbook-metadata-header"},"Cell: ",h(t,o)),r.createElement("div",{className:"flowbook-metadata-section"},r.createElement("div",{className:"flowbook-metadata-item"},r.createElement("span",{style:{fontSize:"0.85em",color:"#666"}},"Id: "),r.createElement("code",{style:{fontSize:"0.85em",color:"#666"}},t))),r.createElement("div",{className:"flowbook-metadata-divider"})),r.createElement("div",{className:"flowbook-metadata-section"},r.createElement("div",{className:"flowbook-metadata-item"},r.createElement("strong",null,"Execution #:")," ",e.execution_seq)),e.errors&&e.errors.length>0&&r.createElement(r.Fragment,null,r.createElement("div",{className:"flowbook-metadata-divider"}),r.createElement("div",{className:"flowbook-metadata-section flowbook-error-section"},r.createElement("div",{className:"flowbook-metadata-item"},r.createElement("strong",{style:{color:"#d32f2f"}},"Errors:"),r.createElement("ul",{className:"flowbook-error-list",style:{margin:"4px 0",paddingLeft:"16px"}},e.errors.map((e,t)=>{return r.createElement("li",{key:t,style:{marginBottom:"6px"}},r.createElement("div",{style:{fontWeight:600,color:"#d32f2f",fontSize:"0.9em"}},{no_read_and_write:"Read And Write Same Location",write_before_read:"Undefined Variable",no_read_before_write:"Forward Contamination",no_write_after_read:"Backward Mutation",unrecoverable_mutation:"Unrecoverable Mutation"}[n=e.error_type]||n),r.createElement("div",{style:{fontSize:"0.85em",color:"#333"}},e.message),e.causer_cell&&r.createElement("div",{style:{fontSize:"0.85em",color:"#666"}},"Conflicts with:"," ",r.createElement("code",null,h(e.causer_cell,o))));var n}))))),(void 0!==e.execute_duration_ms||void 0!==e.code_duration_ms||void 0!==e.state_duration_ms||void 0!==e.check_duration_ms)&&r.createElement(r.Fragment,null,r.createElement("div",{className:"flowbook-metadata-divider"}),r.createElement("div",{className:"flowbook-metadata-section"},r.createElement("div",{className:"flowbook-metadata-item"},r.createElement("strong",null,"Timing:"),r.createElement("ul",{className:"flowbook-timing-list"},void 0!==e.execute_duration_ms&&r.createElement("li",null,"Execute:"," ",r.createElement("code",null,e.execute_duration_ms.toFixed(0)," ms")),void 0!==e.code_duration_ms&&r.createElement("li",null,"Code: ",r.createElement("code",null,e.code_duration_ms.toFixed(0)," ms")),void 0!==e.state_duration_ms&&r.createElement("li",null,"State:"," ",r.createElement("code",null,e.state_duration_ms.toFixed(0)," ms")),void 0!==e.check_duration_ms&&r.createElement("li",null,"Check:"," ",r.createElement("code",null,e.check_duration_ms.toFixed(0)," ms")))))),r.createElement("div",{className:"flowbook-metadata-divider"}),r.createElement("div",{className:"flowbook-metadata-section"},r.createElement("div",{className:"flowbook-metadata-item"},r.createElement("strong",null,"Reads:"),_(s))),r.createElement("div",{className:"flowbook-metadata-divider"}),r.createElement("div",{className:"flowbook-metadata-section"},r.createElement("div",{className:"flowbook-metadata-item"},r.createElement("strong",null,i?"Writes:":"Writes (Intended):"),_(l))),!i&&r.createElement(r.Fragment,null,r.createElement("div",{className:"flowbook-metadata-divider"}),r.createElement("div",{className:"flowbook-metadata-section"},r.createElement("div",{className:"flowbook-metadata-item"},r.createElement("strong",null,"Changed (Actual):"),_(a)))),n&&r.createElement(r.Fragment,null,r.createElement("div",{className:"flowbook-metadata-divider"}),r.createElement("div",{className:"flowbook-metadata-section flowbook-stale-section"},r.createElement("div",{className:"flowbook-metadata-item"},r.createElement("strong",null,"Stale Cells:"),r.createElement("ul",{className:"flowbook-cell-list flowbook-stale"},e.stale_cells.map((e,t)=>r.createElement("li",{key:t},r.createElement("code",null,h(e,o)))))))),e.structural_warnings&&e.structural_warnings.length>0&&r.createElement(r.Fragment,null,r.createElement("div",{className:"flowbook-metadata-divider"}),r.createElement("div",{className:"flowbook-metadata-section flowbook-warning-section"},r.createElement("div",{className:"flowbook-warning-header"},"Structural Warnings"),r.createElement("div",{className:"flowbook-warning-content"},r.createElement("ul",{className:"flowbook-warning-list"},e.structural_warnings.map((e,t)=>r.createElement("li",{key:t},e)))))))},p=({icon:e,text:t,cellRef:o})=>e||t?r.createElement("div",{className:"flowbook-status-header"},r.createElement("div",{style:{fontSize:"0.7em",color:"#888",textTransform:"uppercase",letterSpacing:"0.05em",marginBottom:"2px"}},"Last Execution"),r.createElement("div",{style:{fontSize:"0.85em",fontFamily:"var(--jp-code-font-family, monospace)"}},o&&r.createElement("strong",{style:{marginRight:"6px"}},o),e," ",t)):null;class g extends a.Widget{constructor(){super(),this._metadata=null,this._cellId=null,this._currentCellOrder=[],this._statusIcon=null,this._statusText=null,this._statusCellRef=null,this._root=null,this.id="flowbook-metadata-panel",this.addClass("flowbook-metadata-panel"),this.title.label="FlowBook",this.title.closable=!0,this.title.caption="FlowBook cell metadata",this.render()}render(){this._root||(this._root=(0,i.H)(this.node)),this._root.render(r.createElement(r.Fragment,null,r.createElement(p,{icon:this._statusIcon,text:this._statusText,cellRef:this._statusCellRef}),r.createElement(f,{metadata:this._metadata,cellId:this._cellId,currentCellOrder:this._currentCellOrder})))}updateMetadata(e,t,o){this._metadata=e,this._cellId=t,this._currentCellOrder=o,this.render()}updateStatus(e,t,o){this._statusIcon=e,this._statusText=t,this._statusCellRef=o?h(o,this._currentCellOrder):null,this.render()}clear(){this._metadata=null,this._cellId=null,this._currentCellOrder=[],this.render()}dispose(){this.isDisposed||(this._root?.unmount(),this._root=null,super.dispose())}}var k=o(395);function b(e){return void 0!==e.var_name?e.var_name:"string"==typeof e.qualifier?e.qualifier:void 0}function w(e,t){return"number"==typeof e.qualifier&&"number"==typeof t.qualifier?e.qualifier===t.qualifier:b(e)===b(t)}function y(e,t){switch(e.type){case"var":return"var"===t.type&&e.name===t.name;case"col":return"col"===t.type?w(e,t)&&e.name===t.name:"cols"===t.type&&w(e,t);case"cols":return("col"===t.type||"cols"===t.type)&&w(e,t);case"rows":return("col"===t.type||"rows"===t.type)&&w(e,t);case"file":return"file"===t.type&&e.name===t.name;default:return!1}}function v(e){const t=b(e);return t?`${t}.${e.name}`:e.name}function C(e){return e}o(740);const x={bulge:function({id:e,sourceX:t,sourceY:o,targetX:n,targetY:s,style:l,markerEnd:a,label:i,labelStyle:c,data:d}){const h=d?.bulge||50,u=Math.max(t,n)+h,m=o+.25*(s-o),_=o+.75*(s-o),f=`M ${t},${o} C ${u},${m} ${u},${_} ${n},${s}`,p=.125*t+.375*u+.375*u+.125*n,g=.125*o+.375*m+.375*_+.125*s,b=i?String(i):"",w=6.5*b.length;return r.createElement(r.Fragment,null,r.createElement(k.BaseEdge,{id:e,path:f,style:l,markerEnd:a}),b&&r.createElement("g",{transform:`translate(${p}, ${g})`},r.createElement("rect",{x:-w/2-4,y:-11,width:w+8,height:22,rx:3,ry:3,fill:"white",fillOpacity:.95,stroke:"var(--jp-border-color2, #ddd)",strokeWidth:.5}),r.createElement("text",{style:c,textAnchor:"middle",dominantBaseline:"central",className:"react-flow__edge-text"},b)))}},E={cell:function({data:e}){return r.createElement("div",{className:e.className},r.createElement(k.Handle,{type:"target",position:k.Position.Top,id:"center-top",style:{opacity:0}}),r.createElement(k.Handle,{type:"source",position:k.Position.Bottom,id:"center-bottom",style:{opacity:0}}),r.createElement(k.Handle,{type:"source",position:k.Position.Right,id:"right-source",style:{opacity:0}}),r.createElement(k.Handle,{type:"target",position:k.Position.Right,id:"right-target",style:{opacity:0}}),e.label)}},N={type:k.MarkerType.ArrowClosed,color:"#999",width:12,height:12},S={type:k.MarkerType.ArrowClosed,color:"#2472c8",width:14,height:14},M={type:k.MarkerType.ArrowClosed,color:"#dc3545",width:14,height:14};const $=({cellData:e})=>{const[t,o]=r.useState(null),{nodes:n,allEdges:s}=r.useMemo(()=>function(e){return{nodes:e.map((e,t)=>{let o="flowbook-dep-node";return e.hasError?o+=" flowbook-dep-node-error":e.isStale?o+=" flowbook-dep-node-stale":e.isExecuted||(o+=" flowbook-dep-node-unexecuted"),{id:e.cellId,type:"cell",position:{x:40,y:80*t},data:{label:e.label,className:o}}}),allEdges:function(e){const t=[];for(let o=0;o<e.length;o++)if(e[o].isExecuted)for(let n=o+1;n<e.length;n++){if(!e[n].isExecuted)continue;const s=n-o,l=[],a=new Set;for(const t of e[o].writeLocs)for(const o of e[n].readLocs)if(y(t,o)){const e=v(o);a.has(e)||(a.add(e),l.push(e))}l.length>0&&t.push({sourceId:e[o].cellId,targetId:e[n].cellId,kind:"data_dependency",locations:l,span:s,lane:0})}const o=new Map;for(let t=0;t<e.length;t++)o.set(e[t].cellId,t);const n=new Set;for(const s of e)for(const e of s.violations){if(!e.causer_cell)continue;const l=e.causer_cell.startsWith("@")?e.causer_cell.slice(1):e.causer_cell,a=o.get(s.cellId),r=o.get(l);if(void 0===a||void 0===r)continue;let i,c;"no_write_after_read"===e.error_type?(i=s.cellId,c=l):"no_read_before_write"===e.error_type?(i=l,c=s.cellId):(i=s.cellId,c=l);const d=`${i}-${c}-violation`;if(n.has(d))continue;n.add(d);const h=o.get(i)??0,u=o.get(c)??0,m=Math.abs(h-u);t.push({sourceId:i,targetId:c,kind:"violation",locations:e.locations,span:m||1,lane:0})}t.sort((e,t)=>e.span-t.span);for(let e=0;e<t.length;e++)t[e].lane=e+1;const s=[];for(let t=0;t<e.length-1;t++)s.push({sourceId:e[t].cellId,targetId:e[t+1].cellId,kind:"program_order",locations:[],span:1,lane:0});return s.concat(t)}(e).map(e=>{const t=`${e.sourceId}-${e.targetId}-${e.kind}`;if("program_order"===e.kind)return{id:t,source:e.sourceId,target:e.targetId,sourceHandle:"center-bottom",targetHandle:"center-top",type:"straight",style:{stroke:"#999",strokeWidth:1},markerEnd:N,data:{edgeKind:"program_order"}};const o=40+25*e.lane,n="violation"===e.kind;return{id:t,source:e.sourceId,target:e.targetId,sourceHandle:"right-source",targetHandle:"right-target",type:"bulge",data:{bulge:o,edgeKind:e.kind},label:(s=e.locations,0===s.length?"":s.length<=3?s.join(", "):s.slice(0,3).join(", ")+", ..."),labelStyle:{fontSize:10,fontFamily:"monospace",fill:n?"#dc3545":void 0},style:n?{stroke:"#dc3545",strokeWidth:2,strokeDasharray:"5,5"}:{stroke:"#2472c8",strokeWidth:2},markerEnd:n?M:S};var s})}}(e),[e]),l=r.useMemo(()=>s.filter(e=>{const o=e.data?.edgeKind;return"program_order"===o||"violation"===o||null!==t&&(e.source===t||e.target===t)}),[s,t]),a=r.useCallback((e,t)=>{o(t.id)},[]),i=r.useCallback(()=>{o(null)},[]);return r.createElement(k.ReactFlow,{nodes:n,edges:l,nodeTypes:E,edgeTypes:x,onNodeMouseEnter:a,onNodeMouseLeave:i,nodesDraggable:!1,nodesConnectable:!1,elementsSelectable:!1,fitView:!0,fitViewOptions:{padding:.15,minZoom:.5,maxZoom:1.2},minZoom:.3,maxZoom:2},r.createElement(k.Controls,{showInteractive:!1}))},I=({cellData:e})=>0===e.length?r.createElement("div",{className:"flowbook-metadata-empty"},r.createElement("p",null,"No cells to display."),r.createElement("p",null,"Execute cells to see dependencies.")):r.createElement("div",{className:"flowbook-dependencies-graph",style:{width:"100%",height:"100%",position:"relative"}},r.createElement("div",{style:{position:"absolute",top:0,left:0,right:0,bottom:0}},r.createElement(k.ReactFlowProvider,null,r.createElement($,{cellData:e}))));class A extends a.Widget{constructor(){super(),this._cellData=[],this._root=null,this.id="flowbook-dependencies-panel",this.addClass("flowbook-dependencies-panel"),this.title.label="Dependencies",this.title.closable=!0,this.title.caption="Cell dependency graph",this.render()}render(){this._root||(this._root=(0,i.H)(this.node)),this._root.render(r.createElement(I,{cellData:this._cellData}))}updateGraph(e){this._cellData=e,this.render()}clear(){this._cellData=[],this.render()}dispose(){this.isDisposed||(this._root?.unmount(),this._root=null,super.dispose())}}class R{constructor(e){this._staleCells=new Set,this._stalenessReasons=new Map,this._stalenessChanged=new s.Signal(this),this._notebook=e,this._setupKernelRestartListener()}get stalenessChanged(){return this._stalenessChanged}get staleCells(){return this._staleCells}isCellStale(e){return this._staleCells.has(e)}setReasons(e,t){this._stalenessReasons.set(e,t)}getReasons(e){return this._stalenessReasons.get(e)||[]}setReason(e,t){this._stalenessReasons.set(e,[t])}getReason(e){const t=this._stalenessReasons.get(e);return t&&t.length>0?t[0]:void 0}updateFromMetadata(e){const t=new Set(this._staleCells);this._staleCells=new Set(e.stale_cells);const o=new Set(e.stale_cells),n=[...o].filter(e=>!t.has(e)),s=[...t].filter(e=>!o.has(e));for(const e of s)this._stalenessReasons.delete(e);let l=!1;if(e.staleness_reasons)for(const[t,o]of Object.entries(e.staleness_reasons)){const e=this._stalenessReasons.get(t);JSON.stringify(e||[])!==JSON.stringify(o)&&(l=!0),this._stalenessReasons.set(t,o)}(n.length>0||s.length>0||l)&&this._stalenessChanged.emit({added:n,removed:s,current:[...this._staleCells]})}clear(){const e=[...this._staleCells];this._staleCells.clear(),this._stalenessReasons.clear(),e.length>0&&this._stalenessChanged.emit({added:[],removed:e,current:[]})}_setupKernelRestartListener(){this._notebook.sessionContext.statusChanged.connect((e,t)=>{"restarting"!==t&&"autorestarting"!==t||this.clear()})}dispose(){this._staleCells.clear(),this._stalenessReasons.clear()}}function O(e){return"message"in e}class W{updateStalenessNotice(e,t,o,n){if("code"!==e.model.type)return;const s=e.model.outputs,l=e.model.getMetadata("flowbook"),a=l?.errors&&l.errors.length>0;let r=!1;for(let e=0;e<s.length;e++){const t=C(s.get(e).toJSON());if(!0===t.metadata?.flowbook_violation_notice){r=!0;break}}if(a||r)return void this._removeNoticesByKey(s,"flowbook_staleness_notice");const i=s.length>0&&!0===s.get(0).toJSON().metadata?.flowbook_staleness_notice;if(t){const t=o.getReason(e.model.id)||{type:"unknown",message:"Dependencies changed"};if("never_executed"===t.type)return void(i&&this._removeNoticesByKey(s,"flowbook_staleness_notice"));const l=this.formatStalenessMessage(t,n,e.model.id),a=l.replace(/`([^`]+)`/g,"<code>$1</code>"),r="writer_conflict"===t.type?"Unresolved Violation":"",c=r?`⚠️ ${r}: ${l}`:`⚠️ ${l}`,d={output_type:"display_data",data:{"text/html":r?`<div class="flowbook-staleness-notice">⚠️ <b>${r}</b>: ${a} </div>`:`<div class="flowbook-staleness-notice">⚠️ ${a} </div>`,"text/plain":c},metadata:{flowbook_staleness_notice:!0}};if(i){const e=s.get(0).toJSON().data?.["text/plain"];if(e===c)return}const h=[d];for(let e=0;e<s.length;e++){const t=s.get(e).toJSON();C(t).metadata?.flowbook_staleness_notice||h.push(t)}s.fromJSON(h)}else i&&this._removeNoticesByKey(s,"flowbook_staleness_notice")}formatStalenessMessage(e,t,o){return O(e)?this._formatFrontendReason(e,t,o):this._formatBackendReason(e,t,o)}updateStalenessMetadata(e,t){const o=e.model.getMetadata("flowbook_staleness");if(!o)return;if(!O(o)||!o.causing_cell)return;const n=this.formatStalenessMessage(o,t,e.model.id);n!==o.message&&e.model.setMetadata("flowbook_staleness",{...o,message:n})}_formatFrontendReason(e,t,o){if("source_edited"===e.type)return"Source code was edited";if(!e.causing_cell)return e.message;const n=t.indexOf(e.causing_cell),s=t.indexOf(o),l=n<0,a=l?"a deleted cell":d(n),r=!l&&s>=0&&n<s?" above":l?"":" below",i=[];if(e.variables)for(const t of e.variables)i.push("`"+t+"`");if(e.columns)for(const[t,o]of Object.entries(e.columns))for(const e of o)i.push("`"+t+"."+e+"`");return"writer_conflict"===e.type&&i.length>0?`Writes ${i.join(", ")} already read by ${a}${r}`:i.length>0?`${i.join(", ")} modified by ${a}${r}`:"unknown"===e.type?`Dependencies modified by ${a}`:e.message}_formatBackendReason(e,t,o){const n="cell_id"in e?e.cell_id:void 0,s="loc"in e?e.loc:void 0,l=t.indexOf(o);let a="",r="",i=!1;if(n){const e=t.indexOf(n);i=e<0,a=i?"a deleted cell":d(e),!i&&l>=0&&(r=e<l?" above":" below")}switch(e.type){case"never_executed":return"Cell has never been executed";case"code_changed":return"Source code was edited";case"forward_stale":return s&&a?`\`${s}\` modified by ${a}${r}`:a?`Input modified by ${a}${r}`:"Input was modified";case"write_overlap":return s&&a?`\`${s}\` also written by ${a}`:a?`Writes conflict with ${a}`:"Write conflict detected";case"backward_stale":return s&&a?`\`${s}\` write conflict with ${a}`:"Write conflict detected";case"no_read_before_write":return s&&a?`Reads \`${s}\` written by ${a} ${r}`:"Reads value written by another cell";case"order_changed":return"Cell order changed";case"no_write_after_read":return s&&a?`Writes \`${s}\` already read by ${a} ${r}`:a?`Writes variable already read by ${a} ${r}`:"Writes variable already read by another cell";default:return"Cell is stale"}}_removeNoticesByKey(e,t){const o=[];let n=!1;for(let s=0;s<e.length;s++){const l=e.get(s).toJSON();C(l).metadata?.[t]?n=!0:o.push(l)}n&&e.fromJSON(o)}}class F{updateViolationNotice(e,t){if("code"!==e.model.type)return!1;const o=e.model.outputs,n=e.model.getMetadata("flowbook"),s=n?.errors;let l=!1,a="";for(let e=0;e<o.length;e++){const t=C(o.get(e).toJSON());if(!0===t.metadata?.flowbook_violation_notice){l=!0,a=t.data?.["text/plain"]||"";break}}if(s&&s.length>0){const{noticeOutput:e,plainText:n}=this._buildViolationNotice(s,t);if(l&&a===n)return!0;const r=[e];for(let e=0;e<o.length;e++){const t=o.get(e).toJSON(),n=C(t),s=!0===n.metadata?.flowbook_violation_notice,l=!0===n.metadata?.flowbook_staleness_notice,a="error"===t.output_type&&("ReproducibilityError"===n.ename||"ReproducibilityViolation"===n.ename),i="display_data"===t.output_type&&n.metadata?.predicate_violation;s||l||a||i||r.push(t)}return o.fromJSON(r),!0}if(l){const e=[];for(let t=0;t<o.length;t++){const n=o.get(t).toJSON(),s=C(n);s.metadata?.flowbook_violation_notice||e.push(n)}o.fromJSON(e)}return!1}_buildViolationNotice(e,t){const o=new Map;for(const n of e){let e=null;if(n.causer_cell&&"string"==typeof n.causer_cell){const o=n.causer_cell.startsWith("@")?n.causer_cell.slice(1):n.causer_cell,s=t.indexOf(o);e=s>=0?d(s):"a deleted cell"}const s=[...n.locations].sort().join(","),l=`${n.error_type}:${s}`;o.has(l)||o.set(l,{errorType:n.error_type,locs:n.locations,causers:[]}),e&&!o.get(l).causers.includes(e)&&o.get(l).causers.push(e)}const n=[],s=[];for(const e of o.values()){const t=e.locs.map(e=>"`"+e+"`").join(", ").replace(/`([^`]+)`/g,"<code>$1</code>"),o=e.causers.join(", ");let l;switch(e.errorType){case"no_write_after_read":l=o?`Writes ${t} already read by ${o}`:`Writes ${t} already read by cell above`;for(const t of e.locs)if(t.includes(".")){const[e,o]=t.split(".");l+=`<br>Use <code>${e}["${o}"]</code> = ... for full-column assignment`}break;case"no_read_before_write":l=o?`Reads ${t} written by ${o} below`:`Reads ${t} written by cell below`;break;case"no_read_and_write":l=`Reads and writes ${t}`;break;case"write_before_read":l=`${t} not defined by any cell above`;break;case"unrecoverable_mutation":l=`${t} was modified in place, which violates rerun consistency`;break;default:l=`Violation on ${t}`}n.push(l),s.push(l.replace(/<code>([^<]+)<\/code>/g,"`$1`"))}const l=n.map(e=>`<div>❌ ${e}</div>`).join(""),a=s.map(e=>`❌ ${e}`).join("\n");return{noticeOutput:{output_type:"display_data",data:{"text/html":`<div class="flowbook-error-notice">${l}</div>`,"text/plain":a},metadata:{flowbook_violation_notice:!0,flowbook_predicate_accepted:e[0].accepted,flowbook_violation_count:e.length}},plainText:a}}}class L{constructor(e,t){this._dependenciesPanel=null,this._depPanelFrameId=null,this._stalenessManagers=new Map,this._pendingRestartUpdate=new Set,this._executedInSession=new Map,this._monitoredNotebooks=new Set,this._stalenessNotice=new W,this._violationNotice=new F,this._isDisposed=!1,this._tracker=e,this._panel=t,this._initialize()}setDependenciesPanel(e){this._dependenciesPanel=e}refreshDependencies(){null!==this._depPanelFrameId&&cancelAnimationFrame(this._depPanelFrameId),this._depPanelFrameId=requestAnimationFrame(()=>{this._depPanelFrameId=null;const e=this._tracker.currentWidget;if(e){const t=this.getStalenessManager(e),o=c(e);this._updateDependenciesPanel(e,t,o)}})}updateStatus(e,t,o){this._panel.updateStatus(e,t,o)}getStalenessManager(e){const t=e.context.path;let o=this._stalenessManagers.get(t);return o||(o=new R(e),this._stalenessManagers.set(t,o),o.stalenessChanged.connect(()=>{this._updateAllCells(e)}),e.disposed.connect(()=>{o?.dispose(),this._stalenessManagers.delete(t),this._monitoredNotebooks.delete(t)})),o}updateCell(e,t,o,n){const s=e.model.id,l=t.isCellStale(s),a=this._executedInSession.get(n),r=void 0!==e.model.getMetadata("flowbook"),i=!(a&&a.has(s)||r),c=e.model.sharedModel.getSource(),d=!c||""===c.trim();if(e.node.classList.remove("flowbook-cell-stale"),e.node.classList.remove("flowbook-cell-unexecuted"),e.node.classList.remove("flowbook-cell-error"),d?this._stalenessNotice.updateStalenessNotice(e,!1,t,o):l?(e.node.classList.add("flowbook-cell-stale"),this._stalenessNotice.updateStalenessNotice(e,!0,t,o)):i?(e.node.classList.add("flowbook-cell-unexecuted"),this._stalenessNotice.updateStalenessNotice(e,!1,t,o)):this._stalenessNotice.updateStalenessNotice(e,!1,t,o),this._violationNotice.updateViolationNotice(e,o)&&e.node.classList.add("flowbook-cell-error"),this._stalenessNotice.updateStalenessMetadata(e,o),this._tracker.activeCell===e){const t=e.model.getMetadata("flowbook"),n=this._tracker.currentWidget;t&&n&&this._panel.updateMetadata(t,s,o)}}dispose(){if(!this._isDisposed){this._isDisposed=!0,this._tracker.currentChanged.disconnect(this._onNotebookChanged,this),this._tracker.activeCellChanged.disconnect(this._onActiveCellChanged,this),n.NotebookActions.executed.disconnect(this._onExecuted,this),null!==this._depPanelFrameId&&cancelAnimationFrame(this._depPanelFrameId);for(const e of this._stalenessManagers.values())e.dispose();this._stalenessManagers.clear(),this._monitoredNotebooks.clear(),this._executedInSession.clear()}}_initialize(){this._tracker.currentChanged.connect(this._onNotebookChanged,this),this._tracker.activeCellChanged.connect(this._onActiveCellChanged,this),n.NotebookActions.executed.connect(this._onExecuted,this),this._tracker.currentWidget&&this._monitorNotebook(this._tracker.currentWidget)}_onNotebookChanged(e,t){t&&this._monitorNotebook(t)}_onActiveCellChanged(e,t){const o=e.currentWidget;if(o)if(t&&"code"===t.model.type){const e=t.model.getMetadata("flowbook"),n=t.model.id,s=c(o);e?this._panel.updateMetadata(e,n,s):this._panel.clear()}else this._panel.clear();else this._panel.clear()}_onExecuted(e,t){const o=this._tracker.currentWidget;if(!o||o.content!==t.notebook)return;const n=o.context.path;let s=this._executedInSession.get(n);s||(s=new Set,this._executedInSession.set(n,s)),s.add(t.cell.model.id)}_monitorNotebook(e){const t=e.context.path;this._monitoredNotebooks.has(t)?this._updateAllCells(e):(this._monitoredNotebooks.add(t),this._updateAllCells(e),e.content.model?.cells.changed.connect(()=>{this._updateAllCells(e),this._updatePanelWithCurrentCellOrder(e)}),e.sessionContext.statusChanged.connect((o,n)=>{"restarting"===n||"autorestarting"===n?(this._pendingRestartUpdate.add(t),this._executedInSession.delete(t),this._clearAllFlowbookMetadata(e)):"idle"===n&&this._pendingRestartUpdate.has(t)&&(this._pendingRestartUpdate.delete(t),this._updateAllCells(e))}))}_updatePanelWithCurrentCellOrder(e){const t=this._tracker.activeCell;if(!t||"code"!==t.model.type)return;const o=t.model.getMetadata("flowbook");if(o){const n=t.model.id,s=c(e);this._panel.updateMetadata(o,n,s)}}_updateAllCells(e){const t=this.getStalenessManager(e),o=c(e),n=e.content.widgets,s=e.context.path;n.forEach(e=>{"code"===e.model.type&&this.updateCell(e,t,o,s)}),this.refreshDependencies()}_updateDependenciesPanel(e,t,o){if(!this._dependenciesPanel)return;const n=[],s=e.content.widgets;for(let e=0;e<s.length;e++){const l=s[e];if("code"!==l.model.type)continue;const a=l.model.id,r=o.indexOf(a);if(r<0)continue;const i=l.model.getMetadata("flowbook"),c=i?.errors||[];let h;try{h=d(r)}catch{h=a}n.push({cellId:a,index:r,label:h,readLocs:i?.read_locs||[],writeLocs:i?.write_locs||[],isStale:t.isCellStale(a),isExecuted:void 0!==i,hasError:c.length>0,violations:c})}n.sort((e,t)=>e.index-t.index),this._dependenciesPanel.updateGraph(n)}_clearAllFlowbookMetadata(e){this.getStalenessManager(e).clear(),e.content.widgets.forEach(e=>{if("code"!==e.model.type)return;e.model.deleteMetadata("flowbook"),e.model.deleteMetadata("flowbook_staleness");const t=e.model.outputs,o=[];for(let e=0;e<t.length;e++){const n=t.get(e).toJSON(),s=C(n);s.metadata?.flowbook_staleness_notice||s.metadata?.flowbook_violation_notice||o.push(n)}o.length!==t.length&&t.fromJSON(o),e.node.classList.remove("flowbook-stale-cell"),e.node.classList.remove("flowbook-unexecuted-cell"),e.node.classList.remove("flowbook-cell-stale"),e.node.classList.remove("flowbook-cell-unexecuted"),e.node.classList.remove("flowbook-cell-error")})}}class D{constructor(e,t){this._editTimers=new Map,this._executedCells=new Set,this._attachedKernel=null,this._listenedCellIds=new Set,this._comm=null,this._isDisposed=!1,this._pendingViolations=[],this._tracker=e,this._highlighter=t,this._setupHooks()}dispose(){if(!this._isDisposed){this._isDisposed=!0,n.NotebookActions.executed.disconnect(this._onCellExecuted,this),n.NotebookActions.executionScheduled.disconnect(this._onExecutionScheduled,this),this._tracker.currentChanged.disconnect(this._setupCellEditListener,this),this._tracker.currentChanged.disconnect(this._setupComm,this);for(const e of this._editTimers.values())clearTimeout(e);if(this._editTimers.clear(),this._comm){try{this._comm.close()}catch{}this._comm=null}this._attachedKernel=null}}sendCommand(e){this._comm?this._comm.send(e):console.warn("ReproducibilityExecutionHook: No comm channel, cannot send command:",e)}_setupHooks(){n.NotebookActions.executed.connect(this._onCellExecuted,this),n.NotebookActions.executionScheduled.connect(this._onExecutionScheduled,this),this._tracker.currentChanged.connect(this._setupCellEditListener,this),this._tracker.currentChanged.connect(this._setupComm,this),this._tracker.currentWidget&&(this._setupCellEditListener(),this._setupComm())}_setupCellEditListener(){const e=this._tracker.currentWidget;if(!e)return;const t=e.content;for(let e=0;e<t.widgets.length;e++)this._attachCellEditListener(t.widgets[e]);t.model?.cells.changed.connect((o,n)=>{for(let e=0;e<t.widgets.length;e++)this._attachCellEditListener(t.widgets[e]);"add"!==n.type&&"remove"!==n.type||this._sendNotebookStructure(e)})}_sendNotebookStructure(e){const t=c(e);t.length>0&&this.sendCommand({type:"notebook_structure",cell_order:t})}_attachCellEditListener(e){if("code"!==e.model.type)return;const t=e.model.id;if(this._listenedCellIds.has(t))return;this._listenedCellIds.add(t);const o=e.model;o.sharedModel.changed.connect((e,n)=>{n.sourceChange&&this._onCellContentChanged(t,o)})}_onCellContentChanged(e,t){if(!this._executedCells.has(e))return;const o=this._editTimers.get(e);o&&clearTimeout(o);const n=setTimeout(()=>{this._sendCellEdited(e,t),this._editTimers.delete(e)},1e3);this._editTimers.set(e,n)}_sendCellEdited(e,t){const o=t.sharedModel.getSource();this.sendCommand({type:"cell_edited",cell_id:e,source:o})}_setupComm(){const e=this._tracker.currentWidget;e&&(this._connectComm(e),e.sessionContext.kernelChanged.connect(()=>{this._attachedKernel=null,this._connectComm(e)}),e.sessionContext.statusChanged.connect((t,o)=>{"restarting"===o?(this._attachedKernel=null,this._comm=null):"idle"===o&&null===this._comm&&this._connectComm(e)}))}_connectComm(e){const t=e.sessionContext.session?.kernel;t&&t!==this._attachedKernel&&(this._attachedKernel=t,this._comm=t.createComm("flowbook"),this._comm.onMsg=this._onCommMessage.bind(this),this._comm.open())}_onCommMessage(e){const t=e.content.data;if(!t||!t.type)return;const o=this._tracker.currentWidget;if(o)switch(t.type){case"metadata":{const{type:e,...n}=t,s=n;if(s.cell_id){const e=this._findCell(o,s.cell_id);if(e){e.model.setMetadata("flowbook",s);const t=this._getCurrentCellOrder(o),n=this._highlighter.getStalenessManager(o);this._highlighter.updateCell(e,n,t,o.context.path),this._highlighter.refreshDependencies()}}this._processMetadataUpdate(o,s);break}case"violation":{const{type:e,...o}=t,n=o;this._pendingViolations.push(n);break}case"status":this._highlighter.updateStatus(t.icon,t.text,t.cell_id)}}_onExecutionScheduled(e,t){const{notebook:o,cell:n}=t,s=this._tracker.currentWidget;if(!s||s.content!==o)return;const l=n.model.id,a=this._editTimers.get(l);a&&(clearTimeout(a),this._editTimers.delete(l));const r=c(s);r.length>0&&this.sendCommand({type:"notebook_structure",cell_order:r})}_onCellExecuted(e,t){const{notebook:o,cell:n}=t;if("code"!==n.model.type)return;this._executedCells.add(n.model.id);const s=this._tracker.currentWidget;if(!s||s.content!==o)return;const l=n.model.id;this._pendingViolations=this._pendingViolations.filter(e=>e.cell_id!==l);const a=this._getCurrentCellOrder(s),r=this._highlighter.getStalenessManager(s);this._highlighter.updateCell(n,r,a,s.context.path),this._highlighter.refreshDependencies()}_getCurrentCellOrder(e){return c(e)}_processMetadataUpdate(e,t){const o=this._highlighter.getStalenessManager(e);try{const n=new Set(o.staleCells),s=new Set(t.stale_cells),l=this._getCurrentCellOrder(e),a=[...s].filter(e=>!n.has(e));for(const n of a){const s=this._findCell(e,n);if(s){const e=s.model.sharedModel.getSource();if(!e||""===e.trim())continue}const a=t.staleness_reasons?.[n];let r;r=a&&a.length>0?this._backendReasonToFrontend(a[0],l):this._computeStalenessReason(e,n,t,l),o.setReason(n,r),s&&s.model.setMetadata("flowbook_staleness",r)}const r=[...n].filter(e=>!s.has(e));for(const t of r){const o=this._findCell(e,t);o&&o.model.deleteMetadata("flowbook_staleness")}}catch(e){console.error("ReproducibilityExecutionHook: Error computing staleness reasons:",e)}o.updateFromMetadata(t)}_backendReasonToFrontend(e,t){const o=e.cell_id,n=e.loc;let s="";if(o){const e=t.indexOf(o);s=e>=0?d(e):o}switch(e.type){case"never_executed":return{type:"unknown",message:"Cell has never been executed"};case"code_changed":return{type:"source_edited",message:"Source code was edited"};case"forward_stale":return n&&s?{type:"variable_modified",causing_cell:o,variables:[n],message:`\`${n}\` was modified by ${s}`}:{type:"variable_modified",causing_cell:o,message:s?`Input modified by ${s}`:"Input was modified"};case"write_overlap":return n&&s?{type:"writer_conflict",causing_cell:o,variables:[n],message:`Write overlap: \`${n}\` also written by ${s}`}:{type:"writer_conflict",causing_cell:o,message:s?`Write overlap with ${s}`:"Write overlap detected"};case"backward_stale":return n&&s?{type:"writer_conflict",causing_cell:o,variables:[n],message:`Write conflict on \`${n}\` with ${s}`}:{type:"writer_conflict",causing_cell:o,message:"Write conflict detected"};case"no_read_before_write":return n&&s?{type:"unknown",causing_cell:o,message:`Reads \`${n}\` from later cell ${s} (forward contamination)`}:{type:"unknown",causing_cell:o,message:"Reads from a later cell"};case"order_changed":return{type:"unknown",message:"Cell order changed"};case"no_write_after_read":return n&&s?{type:"variable_modified",causing_cell:o,variables:[n],message:`Wrote \`${n}\` read by earlier cell ${s} (backward mutation)`}:{type:"unknown",causing_cell:o,message:s?`Wrote to variable read by ${s}`:"Backward mutation detected"};default:return{type:"unknown",causing_cell:o,message:s?`Dependencies changed by ${s}`:"Cell is stale"}}}_computeStalenessReason(e,t,o,n){const s=o.cell_id;if(t===s&&(!o.changed_locs||0===o.changed_locs.length))return{type:"source_edited",causing_cell:s,message:"Source code was edited"};const l=this._findCell(e,t),a=l?.model.metadata,r=a?.flowbook,i=n.indexOf(s),c=i>=0?d(i):s,h=function(e,t){const o=[];for(const n of t)for(const t of e)if(y(t,n)){o.push(n);break}return o}(o.changed_locs||[],r?.read_locs||[]);if(h.length>0){const e=h.map(e=>"`"+v(e)+"`");return{type:"variable_modified",causing_cell:s,variables:h.map(e=>v(e)),message:`${e.join(", ")} modified by ${c}`}}const u=r?.write_locs||[],m=o.read_locs||[],_=[],f=new Set;for(const e of m){const t=`${e.type}:${e.qualifier||""}:${e.name}`;if(!f.has(t))for(const o of u)if(y(o,e)){_.push(v(e)),f.add(t);break}}if(_.length>0){const e=_.map(e=>"`"+e+"`");return{type:"writer_conflict",causing_cell:s,variables:_,message:`Writes ${e.join(", ")}, which was read by ${c}`}}return{type:"unknown",causing_cell:s,message:`Dependencies changed by ${c}`}}_findCell(e,t){const o=e.content.widgets;for(let e=0;e<o.length;e++)if(o[e].model.id===t)return o[e];return null}}class K{constructor(){this._overlays=new Map,this._observers=new Map,this._notebooks=new Map,this._listeners=new Map,this._domListeners=new Map}startMonitoring(e,t){this._overlays.set(e,new Map),this._notebooks.set(e,t),this.updateAllOverlays(e,t);const o=new MutationObserver(o=>{for(const n of o)if("childList"===n.type&&n.addedNodes.length>0)for(const o of n.addedNodes)if(o instanceof HTMLElement&&(o.classList.contains("jp-InputArea-editor")||o.querySelector(".jp-InputArea-editor")))return void this.updateAllOverlays(e,t)});o.observe(t.content.node,{childList:!0,subtree:!0}),this._observers.set(e,o);const n=[],s=()=>{this.updateAllOverlays(e,t)};t.content.model?.cells.changed.connect(s),n.push({signal:t.content.model?.cells.changed,callback:s}),this._listeners.set(e,n);const l=t.content.node,a=()=>{requestAnimationFrame(()=>{this.updateAllOverlays(e,t)})},r=[];for(const e of["cut","paste"])l.addEventListener(e,a,!0),r.push({event:e,handler:a});const i=e=>{const t=e;(t.metaKey||t.ctrlKey)&&"z"===t.key&&a()};l.addEventListener("keydown",i,!0),r.push({event:"keydown",handler:i}),this._domListeners.set(e,{node:l,handlers:r})}stopMonitoring(e){const t=this._observers.get(e);t&&(t.disconnect(),this._observers.delete(e));const o=this._overlays.get(e);o&&(o.forEach(e=>{e.remove()}),this._overlays.delete(e)),this._notebooks.delete(e);const n=this._listeners.get(e);n&&(n.forEach(({signal:e,callback:t})=>{try{e?.disconnect(t)}catch(e){}}),this._listeners.delete(e));const s=this._domListeners.get(e);if(s){for(const{event:e,handler:t}of s.handlers)s.node.removeEventListener(e,t,!0);this._domListeners.delete(e)}}updateAllOverlays(e,t){const o=this._overlays.get(e);if(!o)return;const n=new Map;let s=0;t.content.widgets.forEach(e=>{"code"===e.model.type&&(n.set(e.model.id,s),s++)}),t.content.widgets.forEach(e=>{if("code"===e.model.type){const t=e.model.id,s=n.get(t);if(void 0===s)return;const l=o.get(t);if(l&&l.parentNode)return void(l.textContent=d(s));const a=e.node.querySelector(".jp-InputArea-editor");if(a){l&&l.remove();const e=this.createOverlay(s);a.style.position="relative",a.appendChild(e),o.set(t,e)}}})}createOverlay(e){const t=document.createElement("div");return t.className="flowbook-cell-index-overlay",t.textContent=d(e),t}}var T=o(516),P=o(562);class H{constructor(e){this._highlighter=null,this._kernelDetector=e}setHighlighter(e){this._highlighter=e}createNew(e,t){const o=new T.ToolbarButton({icon:P.stepIntoIcon,tooltip:"Run all stale and unrun cells",onClick:async()=>{await this._runAllActionable(e)}});o.node.style.display="none",e.toolbar.insertItem(10,"flowbook-run-next-stale",o);const n=()=>{const t=this._kernelDetector.isFlowbookKernel(e);o.node.style.display=t?"":"none"};return e.sessionContext.ready.then(()=>{n()}),e.sessionContext.kernelChanged.connect(()=>{n()}),{dispose:()=>{o.dispose()},get isDisposed(){return o.isDisposed}}}async _runAllActionable(e){const t=e.content;for(let o=0;o<500;o++){let o=new Set;this._highlighter&&(o=this._highlighter.getStalenessManager(e).staleCells);let s=-1;const l=t.widgets;for(let e=0;e<l.length;e++){const t=l[e];if("code"!==t.model.type)continue;const n=t.model,a=n.sharedModel.getSource();if(!a||""===a.trim())continue;const r=t.model.id;if(o.has(r)||null===n.executionCount){s=e;break}}if(s<0)break;t.activeCellIndex=s,t.scrollToCell(l[s]),await n.NotebookActions.run(t,e.sessionContext);const a=l[s],r=a.model.outputs;let i=!1;if(r)for(let e=0;e<r.length;e++){const t=r.get(e);if(t&&"error"===t.type){i=!0;break}}if(i)break;const c=a.model.getMetadata("flowbook");if(c?.errors&&c.errors.length>0)break}}}var j=o(892);function q(e,t){const o=e.content.widgets;let n=0;for(let e=0;e<o.length;e++)if("code"===o[e].model.type){if(n===t)return e;n++}throw new Error(`Code cell index ${t} out of range (have ${n} code cells)`)}function B(e){const t=e.model?.outputs;if(!t)return"";const o=[];for(let e=0;e<t.length;e++){const n=t.get(e);if(!n)continue;const s=n.type;if("stream"===s)o.push(n.data?.["text/plain"]||n.text||"");else if("execute_result"===s||"display_data"===s){const e=n.data?.["text/plain"];e&&o.push(e)}else if("error"===s){const e=n.traceback;if(e){const t=String.fromCharCode(27),n=new RegExp(t+"\\[[0-9;]*m","g");o.push(e.join("\n").replace(n,""))}else o.push(`${n.ename}: ${n.evalue}`)}}return o.join("\n")}function J(e,t){const o=e.content.widgets[t];if(!o||"code"!==o.model.type)return!1;const n=o.model.outputs;if(!n)return!1;for(let e=0;e<n.length;e++){const t=n.get(e);if(t&&"error"===t.type)return!0}return!1}let V=null,z=null,U=null,Z=null;function G(e,t,o,n){V=e,z=t,U=o,Z=n}function X(){const e=Z?.currentWidget;if(!e)throw new Error("No active notebook");return e}function Y(){if(!V)throw new Error("FlowBook not active");return V.getStalenessManager(X())}class Q{constructor(e,t,o,n){this._panel=null,this._dependenciesPanel=null,this._highlighter=null,this._executionHook=null,this._isActive=!1,this._activeNotebookPath=null,this._statusListenerNotebook=null,this._app=e,this._tracker=t,this._kernelDetector=o,this._cellIndexManager=new K,this._toolbarExtension=n,this._setupKernelChangeListener(),this._checkCurrentNotebook()}_setupKernelChangeListener(){this._kernelDetector.kernelChanged.connect((e,t)=>{"flowbook_kernel"===t.currentKernel?this._activate():"flowbook_kernel"===t.previousKernel&&this._deactivate()}),this._tracker.currentChanged.connect(()=>{this._checkCurrentNotebook()})}_checkCurrentNotebook(){const e=this._tracker.currentWidget;e&&(e.sessionContext.ready.then(()=>{this._kernelDetector.isFlowbookKernel(e)?this._activate():this._deactivate()}),this._statusListenerNotebook&&this._statusListenerNotebook!==e&&this._statusListenerNotebook.sessionContext.statusChanged.disconnect(this._onStatusChanged,this),this._statusListenerNotebook!==e&&(e.sessionContext.statusChanged.connect(this._onStatusChanged,this),this._statusListenerNotebook=e))}_onStatusChanged(){const e=this._statusListenerNotebook;if(!e)return;const t=this._kernelDetector.isFlowbookKernel(e);t&&!this._isActive?this._activate():t&&this._isActive?this._writeKernelDiscovery(e):!t&&this._isActive&&this._deactivate()}_activate(){if(this._isActive)return;this._panel=new g,this._app.shell.add(this._panel,"right",{rank:510}),this._dependenciesPanel=new A,this._app.shell.add(this._dependenciesPanel,"right",{rank:520}),this._highlighter=new L(this._tracker,this._panel),this._highlighter.setDependenciesPanel(this._dependenciesPanel),this._toolbarExtension.setHighlighter(this._highlighter),this._executionHook=new D(this._tracker,this._highlighter),this._highlighter.refreshDependencies(),G(this._highlighter,this._executionHook,this._kernelDetector,this._tracker);const e=this._tracker.currentWidget;e&&(this._activeNotebookPath=e.context.path,this._cellIndexManager.startMonitoring(this._activeNotebookPath,e),this._syncInitialState(e),this._writeKernelDiscovery(e)),this._isActive=!0}_syncInitialState(e){if(!this._executionHook)return;const t=c(e);0!==t.length&&(this._executionHook.sendCommand({type:"notebook_structure",cell_order:t}),this._executionHook.sendCommand({type:"sync"}))}_writeKernelDiscovery(e){const t=e.sessionContext.session;if(!t||!t.kernel)return;const o=e.context.path,n=t.kernel.id;(async function(e="",t={}){const o=j.ServerConnection.makeSettings(),n=`${o.baseUrl}flowbook/${e}`;let s;try{s=await j.ServerConnection.makeRequest(n,t,o)}catch(e){throw new j.ServerConnection.NetworkError(e)}let l=await s.text();if(l.length>0)try{l=JSON.parse(l)}catch{}if(!s.ok){const e=l.error||l.message||l;throw new j.ServerConnection.ResponseError(s,e)}return l})(`kernel-discovery/${encodeURIComponent(o)}`,{method:"PUT",body:JSON.stringify({kernel_name:t.kernel.name,connection_file:`kernel-${n}.json`,pid:0})}).catch(()=>{})}_deactivate(){this._isActive&&(this._activeNotebookPath&&(this._cellIndexManager.stopMonitoring(this._activeNotebookPath),this._activeNotebookPath=null),this._panel&&(this._panel.dispose(),this._panel=null),this._dependenciesPanel&&(this._dependenciesPanel.dispose(),this._dependenciesPanel=null),this._highlighter&&(this._highlighter.dispose(),this._highlighter=null),this._executionHook&&(this._executionHook.dispose(),this._executionHook=null),G(null,null,null,null),this._isActive=!1)}}const ee=[{id:"flowbook:plugin",autoStart:!0,requires:[n.INotebookTracker],activate:(e,t)=>{window.app=e;const o=new l(t);!function(e,t,o){e.commands.addCommand("flowbook:exec-restore",{label:"Run with upstream state",isEnabled:()=>{try{const e=t.currentWidget;if(!e)return!1;if(!o.isFlowbookKernel(e))return!1;const n=e.content.activeCell;if(!n||"code"!==n.model.type)return!1;const s=n.model.getMetadata("flowbook"),l=s?.errors;return void 0!==l&&l.some(e=>"no_read_before_write"===e.error_type)}catch(e){return console.error("FlowBook exec-restore isEnabled error:",e),!1}},isVisible:()=>{try{const e=t.currentWidget;return!!e&&o.isFlowbookKernel(e)}catch{return!1}},execute:async()=>{const o=t.currentWidget;if(!o)return;const n=o.content.activeCell;if(!n||"code"!==n.model.type)return;const s=n.model.id,l=o.sessionContext.session;if(!l||!l.kernel)return;const a=l.kernel.requestExecute({code:`%exec_restore ${s}`,silent:!0,store_history:!1});await a.done,await e.commands.execute("notebook:run-cell")}})}(e,t,o),function(e,t,o){Z=t,U=o,e.commands.addCommand("flowbook:is-active",{execute:()=>{const e=t.currentWidget;if(!e||!U)return{active:!1};const o=U.isFlowbookKernel(e);return{active:o,kernel:o?"flowbook_kernel":e.sessionContext.session?.kernel?.name||"unknown"}}}),e.commands.addCommand("flowbook:get-metadata",{execute:e=>{const t=X(),o=e.cellIndex,n=q(t,o),s=t.content.widgets[n],l=s.model.getMetadata("flowbook");return{label:d(o),cell_id:s.model.id,...l||{},stale_cells_labels:l?.stale_cells?.map(e=>{const o=c(t).indexOf(e);return o>=0?d(o):e})}}}),e.commands.addCommand("flowbook:get-stale-cells",{execute:()=>{const e=X(),t=Y(),o=t.staleCells,n=c(e),s=[];return n.forEach((e,n)=>{if(o.has(e)){const o=t.getReason(e),l=o&&"message"in o?o.message:o?.type||"stale";s.push({index:n,label:d(n),cell_id:e,reason:l})}}),s}}),e.commands.addCommand("flowbook:get-next-actionable",{execute:()=>{const e=X(),t=e.content.widgets,o=V?Y().staleCells:new Set;let n=0,s=null,l=null;for(let a=0;a<t.length;a++){const r=t[a];if("code"!==r.model.type)continue;const i=r.model,c=i.sharedModel.getSource(),h=r.model.id,u=n;if(n++,!c||""===c.trim())continue;const m=r.model.getMetadata("flowbook");if(m?.errors&&m.errors.length>0)return{index:u,label:d(u),cell_id:h,reason:"error",error:m.errors[0].message};if(J(e,a))return{index:u,label:d(u),cell_id:h,reason:"error"};!s&&o.has(h)&&(s={index:u,label:d(u),cell_id:h,reason:"stale"}),l||null!==i.executionCount||(l={index:u,label:d(u),cell_id:h,reason:"unexecuted"})}return s||l||{done:!0}}}),e.commands.addCommand("flowbook:get-status",{execute:()=>{const e=X(),t=e.content.widgets,o=V?Y().staleCells:new Set;let n=0,s=0,l=0,a=0,r=0;for(let i=0;i<t.length;i++){const c=t[i];if("code"!==c.model.type)continue;n++;const d=c.model,h=d.sharedModel.getSource();if(!h||""===h.trim()){r++;continue}null!==d.executionCount&&s++,o.has(c.model.id)&&l++;const u=c.model.getMetadata("flowbook");u?.errors&&u.errors.length>0&&a++,J(e,i)&&a++}const i=n-r;return{total_code_cells:n,non_empty:i,executed:s,stale:l,clean:s-l,with_errors:a,reproducible:i>0&&s===i&&0===l&&0===a}}}),e.commands.addCommand("flowbook:get-cell-count",{execute:()=>{const e=X().content.widgets;let t=0,o=0;for(let n=0;n<e.length;n++)"code"===e[n].model.type?t++:"markdown"===e[n].model.type&&o++;return{total:e.length,code_cells:t,markdown_cells:o}}}),e.commands.addCommand("flowbook:get-cell",{execute:e=>{const t=X(),o=e.cellIndex,n=q(t,o),s=t.content.widgets[n],l=d(o),a=s.model,r=s.model.getMetadata("flowbook");return{label:l,cell_id:s.model.id,cell_type:s.model.type,source:a.sharedModel.getSource(),execution_count:a.executionCount,outputs_text:B(s),flowbook_meta:r||null}}}),e.commands.addCommand("flowbook:get-cell-output",{execute:e=>{const t=X(),o=e.cellIndex,n=q(t,o),s=t.content.widgets[n];return{label:d(o),outputs_text:B(s)}}}),e.commands.addCommand("flowbook:edit-cell-source",{execute:e=>{const t=X(),o=e.cellIndex,n=e.source,s=q(t,o),l=t.content.widgets[s];return l.model.sharedModel.setSource(n),{label:d(o),cell_id:l.model.id}}}),e.commands.addCommand("flowbook:move-cell",{execute:e=>{const t=X(),o=e.fromIndex,n=e.toIndex,s=q(t,o),l=q(t,n),a=t.content.model?.sharedModel;if(!a)throw new Error("No shared model");return a.moveCell(s,l),{label:d(o),newIndex:n}}}),e.commands.addCommand("flowbook:notify-structure",{execute:()=>{const e=X();if(z){const t=c(e);z.sendCommand({type:"notebook_structure",cell_order:t})}}}),e.commands.addCommand("flowbook:set-continue-after-violation",{execute:e=>{const t=e.enabled;return z&&z.sendCommand({type:"continue_after_violation",enabled:t}),{enabled:t}}}),e.commands.addCommand("flowbook:run-cell",{execute:async e=>{const t=X(),o=e.cellIndex,s=q(t,o),l=d(o);t.content.activeCellIndex=s,await n.NotebookActions.run(t.content,t.sessionContext);const a=t.content.widgets[s],r=a.model.getMetadata("flowbook"),i=J(t,s),c=function(e,t){const o=e.content.widgets[t];if(!o||"code"!==o.model.type)return[];const n=o.model.getMetadata("flowbook");return n?.errors||[]}(t,s);return{label:l,cell_id:a.model.id,status:i?"error":c.length>0?"violation":"ok",outputs_text:B(a),flowbook_meta:r||null,errors:c}}}),e.commands.addCommand("flowbook:run-actionable-cells",{execute:async()=>{X();const t=[];let o=0;for(;o<500;)try{const n=await e.commands.execute("flowbook:get-next-actionable");if(n.done)break;const s=n.index,l=n.label,a=await e.commands.execute("flowbook:run-cell",{cellIndex:s});if(o++,t.push({label:l,status:a.status,outputs_preview:(a.outputs_text||"").slice(0,200)}),"error"===a.status)break;if("violation"===a.status)break}catch(e){console.error("Error in run-actionable-cells:",e);break}return{results:t,cells_run:o,summary:await e.commands.execute("flowbook:get-status")}}})}(e,t,o);const s=new H(o);e.docRegistry.addWidgetExtension("Notebook",s),new Q(e,t,o,s)}}]}}]);
@@ -1 +1 @@
1
- var _JUPYTERLAB;(()=>{"use strict";var e,r,t,o,n,a,i,l,u,f,s,d,c,p,h,v,b,m,g,y,w,k,S,j={935(e,r,t){var o={"./index":()=>Promise.all([t.e(951),t.e(638),t.e(873)]).then(()=>()=>t(873)),"./extension":()=>Promise.all([t.e(951),t.e(638),t.e(873)]).then(()=>()=>t(873)),"./style":()=>t.e(728).then(()=>()=>t(728))},n=(e,r)=>(t.R=r,r=t.o(o,e)?o[e]():Promise.resolve().then(()=>{throw new Error('Module "'+e+'" does not exist in container.')}),t.R=void 0,r),a=(e,r)=>{if(t.S){var o="default",n=t.S[o];if(n&&n!==e)throw new Error("Container initialization failed as it has already been initialized with a different share scope");return t.S[o]=e,t.I(o,r)}};t.d(r,{get:()=>n,init:()=>a})}},P={};function E(e){var r=P[e];if(void 0!==r)return r.exports;var t=P[e]={id:e,exports:{}};return j[e](t,t.exports,E),t.exports}E.m=j,E.c=P,E.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return E.d(r,{a:r}),r},E.d=(e,r)=>{for(var t in r)E.o(r,t)&&!E.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},E.f={},E.e=e=>Promise.all(Object.keys(E.f).reduce((r,t)=>(E.f[t](e,r),r),[])),E.u=e=>e+"."+{728:"b1df4bca1a3305d0d0a7",873:"3ca7ae352f965bccc339",905:"94c2bfb401597cc2a103",951:"ba84389925d6a0676e79"}[e]+".js?v="+{728:"b1df4bca1a3305d0d0a7",873:"3ca7ae352f965bccc339",905:"94c2bfb401597cc2a103",951:"ba84389925d6a0676e79"}[e],E.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),E.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),e={},r="flowbook:",E.l=(t,o,n,a)=>{if(e[t])e[t].push(o);else{var i,l;if(void 0!==n)for(var u=document.getElementsByTagName("script"),f=0;f<u.length;f++){var s=u[f];if(s.getAttribute("src")==t||s.getAttribute("data-webpack")==r+n){i=s;break}}i||(l=!0,(i=document.createElement("script")).charset="utf-8",E.nc&&i.setAttribute("nonce",E.nc),i.setAttribute("data-webpack",r+n),i.src=t),e[t]=[o];var d=(r,o)=>{i.onerror=i.onload=null,clearTimeout(c);var n=e[t];if(delete e[t],i.parentNode&&i.parentNode.removeChild(i),n&&n.forEach(e=>e(o)),r)return r(o)},c=setTimeout(d.bind(null,void 0,{type:"timeout",target:i}),12e4);i.onerror=d.bind(null,i.onerror),i.onload=d.bind(null,i.onload),l&&document.head.appendChild(i)}},E.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{E.S={};var e={},r={};E.I=(t,o)=>{o||(o=[]);var n=r[t];if(n||(n=r[t]={}),!(o.indexOf(n)>=0)){if(o.push(n),e[t])return e[t];E.o(E.S,t)||(E.S[t]={});var a=E.S[t],i="flowbook",l=(e,r,t,o)=>{var n=a[e]=a[e]||{},l=n[r];(!l||!l.loaded&&(!o!=!l.eager?o:i>l.from))&&(n[r]={get:t,from:i,eager:!!o})},u=[];return"default"===t&&(l("@xyflow/react","12.10.2",()=>Promise.all([E.e(905),E.e(638)]).then(()=>()=>E(905))),l("flowbook","0.1.0",()=>Promise.all([E.e(951),E.e(638),E.e(873)]).then(()=>()=>E(873)))),e[t]=u.length?Promise.all(u).then(()=>e[t]=1):1}}})(),(()=>{var e;E.g.importScripts&&(e=E.g.location+"");var r=E.g.document;if(!e&&r&&(r.currentScript&&"SCRIPT"===r.currentScript.tagName.toUpperCase()&&(e=r.currentScript.src),!e)){var t=r.getElementsByTagName("script");if(t.length)for(var o=t.length-1;o>-1&&(!e||!/^http(s?):/.test(e));)e=t[o--].src}if(!e)throw new Error("Automatic publicPath is not supported in this browser");e=e.replace(/^blob:/,"").replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),E.p=e})(),t=e=>{var r=e=>e.split(".").map(e=>+e==e?+e:e),t=/^([^-+]+)?(?:-([^+]+))?(?:\+(.+))?$/.exec(e),o=t[1]?r(t[1]):[];return t[2]&&(o.length++,o.push.apply(o,r(t[2]))),t[3]&&(o.push([]),o.push.apply(o,r(t[3]))),o},o=(e,r)=>{e=t(e),r=t(r);for(var o=0;;){if(o>=e.length)return o<r.length&&"u"!=(typeof r[o])[0];var n=e[o],a=(typeof n)[0];if(o>=r.length)return"u"==a;var i=r[o],l=(typeof i)[0];if(a!=l)return"o"==a&&"n"==l||"s"==l||"u"==a;if("o"!=a&&"u"!=a&&n!=i)return n<i;o++}},n=e=>{var r=e[0],t="";if(1===e.length)return"*";if(r+.5){t+=0==r?">=":-1==r?"<":1==r?"^":2==r?"~":r>0?"=":"!=";for(var o=1,a=1;a<e.length;a++)o--,t+="u"==(typeof(l=e[a]))[0]?"-":(o>0?".":"")+(o=2,l);return t}var i=[];for(a=1;a<e.length;a++){var l=e[a];i.push(0===l?"not("+u()+")":1===l?"("+u()+" || "+u()+")":2===l?i.pop()+" "+i.pop():n(l))}return u();function u(){return i.pop().replace(/^\((.+)\)$/,"$1")}},a=(e,r)=>{if(0 in e){r=t(r);var o=e[0],n=o<0;n&&(o=-o-1);for(var i=0,l=1,u=!0;;l++,i++){var f,s,d=l<e.length?(typeof e[l])[0]:"";if(i>=r.length||"o"==(s=(typeof(f=r[i]))[0]))return!u||("u"==d?l>o&&!n:""==d!=n);if("u"==s){if(!u||"u"!=d)return!1}else if(u)if(d==s)if(l<=o){if(f!=e[l])return!1}else{if(n?f>e[l]:f<e[l])return!1;f!=e[l]&&(u=!1)}else if("s"!=d&&"n"!=d){if(n||l<=o)return!1;u=!1,l--}else{if(l<=o||s<d!=n)return!1;u=!1}else"s"!=d&&"n"!=d&&(u=!1,l--)}}var c=[],p=c.pop.bind(c);for(i=1;i<e.length;i++){var h=e[i];c.push(1==h?p()|p():2==h?p()&p():h?a(h,r):!p())}return!!p()},i=(e,r)=>e&&E.o(e,r),l=e=>(e.loaded=1,e.get()),u=e=>Object.keys(e).reduce((r,t)=>(e[t].eager&&(r[t]=e[t]),r),{}),f=(e,r,t,n)=>{var i=n?u(e[r]):e[r];return(r=Object.keys(i).reduce((e,r)=>!a(t,r)||e&&!o(e,r)?e:r,0))&&i[r]},s=(e,r,t)=>{var n=t?u(e[r]):e[r];return Object.keys(n).reduce((e,r)=>!e||!n[e].loaded&&o(e,r)?r:e,0)},d=(e,r,t,o)=>"Unsatisfied version "+t+" from "+(t&&e[r][t].from)+" of shared singleton module "+r+" (required "+n(o)+")",c=(e,r,t,o,a)=>{var i=e[t];return"No satisfying version ("+n(o)+")"+(a?" for eager consumption":"")+" of shared module "+t+" found in shared scope "+r+".\nAvailable versions: "+Object.keys(i).map(e=>e+" from "+i[e].from).join(", ")},p=e=>{throw new Error(e)},h=e=>{"undefined"!=typeof console&&console.warn&&console.warn(e)},b=(e,r,t)=>t?t():((e,r)=>p("Shared module "+r+" doesn't exist in shared scope "+e))(e,r),m=(v=e=>function(r,t,o,n,a){var i=E.I(r);return i&&i.then&&!o?i.then(e.bind(e,r,E.S[r],t,!1,n,a)):e(r,E.S[r],t,o,n,a)})((e,r,t,o,n,a)=>{if(!i(r,t))return b(e,t,a);var u=f(r,t,n,o);return u?l(u):a?a():void p(c(r,e,t,n,o))}),g=v((e,r,t,o,n,u)=>{if(!i(r,t))return b(e,t,u);var f=s(r,t,o);return a(n,f)||h(d(r,t,f,n)),l(r[t][f])}),y={},w={345:()=>g("default","react",!1,[1,18,2,0]),628:()=>g("default","react-dom",!1,[1,18,2,0]),247:()=>g("default","@lumino/widgets",!1,[1,2,3,1,,"alpha",1]),395:()=>m("default","@xyflow/react",!1,[1,12,10,1],()=>E.e(905).then(()=>()=>E(905))),537:()=>g("default","@jupyterlab/apputils",!1,[1,4,6,7]),602:()=>g("default","@lumino/signaling",!1,[1,2,0,0]),615:()=>g("default","@jupyterlab/notebook",!1,[1,4,5,7]),651:()=>g("default","@jupyterlab/ui-components",!1,[1,4,5,7]),697:()=>g("default","@jupyterlab/services",!1,[1,7,5,7])},k={638:[345,628],873:[247,395,537,602,615,651,697]},S={},E.f.consumes=(e,r)=>{E.o(k,e)&&k[e].forEach(e=>{if(E.o(y,e))return r.push(y[e]);if(!S[e]){var t=r=>{y[e]=0,E.m[e]=t=>{delete E.c[e],t.exports=r()}};S[e]=!0;var o=r=>{delete y[e],E.m[e]=t=>{throw delete E.c[e],r}};try{var n=w[e]();n.then?r.push(y[e]=n.then(t).catch(o)):t(n)}catch(e){o(e)}}})},(()=>{var e={952:0};E.f.j=(r,t)=>{var o=E.o(e,r)?e[r]:void 0;if(0!==o)if(o)t.push(o[2]);else if(638!=r){var n=new Promise((t,n)=>o=e[r]=[t,n]);t.push(o[2]=n);var a=E.p+E.u(r),i=new Error;E.l(a,t=>{if(E.o(e,r)&&(0!==(o=e[r])&&(e[r]=void 0),o)){var n=t&&("load"===t.type?"missing":t.type),a=t&&t.target&&t.target.src;i.message="Loading chunk "+r+" failed.\n("+n+": "+a+")",i.name="ChunkLoadError",i.type=n,i.request=a,o[1](i)}},"chunk-"+r,r)}else e[r]=0};var r=(r,t)=>{var o,n,[a,i,l]=t,u=0;if(a.some(r=>0!==e[r])){for(o in i)E.o(i,o)&&(E.m[o]=i[o]);l&&l(E)}for(r&&r(t);u<a.length;u++)n=a[u],E.o(e,n)&&e[n]&&e[n][0](),e[n]=0},t=self.webpackChunkflowbook=self.webpackChunkflowbook||[];t.forEach(r.bind(null,0)),t.push=r.bind(null,t.push.bind(t))})(),E.nc=void 0;var x=E(935);(_JUPYTERLAB=void 0===_JUPYTERLAB?{}:_JUPYTERLAB).flowbook=x})();
1
+ var _JUPYTERLAB;(()=>{"use strict";var e,r,t,o,n,a,i,l,u,f,s,d,c,p,h,v,b,m,g,y,w,k,S,j={935(e,r,t){var o={"./index":()=>Promise.all([t.e(951),t.e(638),t.e(873)]).then(()=>()=>t(873)),"./extension":()=>Promise.all([t.e(951),t.e(638),t.e(873)]).then(()=>()=>t(873)),"./style":()=>t.e(728).then(()=>()=>t(728))},n=(e,r)=>(t.R=r,r=t.o(o,e)?o[e]():Promise.resolve().then(()=>{throw new Error('Module "'+e+'" does not exist in container.')}),t.R=void 0,r),a=(e,r)=>{if(t.S){var o="default",n=t.S[o];if(n&&n!==e)throw new Error("Container initialization failed as it has already been initialized with a different share scope");return t.S[o]=e,t.I(o,r)}};t.d(r,{get:()=>n,init:()=>a})}},P={};function E(e){var r=P[e];if(void 0!==r)return r.exports;var t=P[e]={id:e,exports:{}};return j[e](t,t.exports,E),t.exports}E.m=j,E.c=P,E.n=e=>{var r=e&&e.__esModule?()=>e.default:()=>e;return E.d(r,{a:r}),r},E.d=(e,r)=>{for(var t in r)E.o(r,t)&&!E.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},E.f={},E.e=e=>Promise.all(Object.keys(E.f).reduce((r,t)=>(E.f[t](e,r),r),[])),E.u=e=>e+"."+{728:"b1df4bca1a3305d0d0a7",873:"3edec525c5c79ec55cd6",905:"94c2bfb401597cc2a103",951:"ba84389925d6a0676e79"}[e]+".js?v="+{728:"b1df4bca1a3305d0d0a7",873:"3edec525c5c79ec55cd6",905:"94c2bfb401597cc2a103",951:"ba84389925d6a0676e79"}[e],E.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"==typeof window)return window}}(),E.o=(e,r)=>Object.prototype.hasOwnProperty.call(e,r),e={},r="flowbook:",E.l=(t,o,n,a)=>{if(e[t])e[t].push(o);else{var i,l;if(void 0!==n)for(var u=document.getElementsByTagName("script"),f=0;f<u.length;f++){var s=u[f];if(s.getAttribute("src")==t||s.getAttribute("data-webpack")==r+n){i=s;break}}i||(l=!0,(i=document.createElement("script")).charset="utf-8",E.nc&&i.setAttribute("nonce",E.nc),i.setAttribute("data-webpack",r+n),i.src=t),e[t]=[o];var d=(r,o)=>{i.onerror=i.onload=null,clearTimeout(c);var n=e[t];if(delete e[t],i.parentNode&&i.parentNode.removeChild(i),n&&n.forEach(e=>e(o)),r)return r(o)},c=setTimeout(d.bind(null,void 0,{type:"timeout",target:i}),12e4);i.onerror=d.bind(null,i.onerror),i.onload=d.bind(null,i.onload),l&&document.head.appendChild(i)}},E.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},(()=>{E.S={};var e={},r={};E.I=(t,o)=>{o||(o=[]);var n=r[t];if(n||(n=r[t]={}),!(o.indexOf(n)>=0)){if(o.push(n),e[t])return e[t];E.o(E.S,t)||(E.S[t]={});var a=E.S[t],i="flowbook",l=(e,r,t,o)=>{var n=a[e]=a[e]||{},l=n[r];(!l||!l.loaded&&(!o!=!l.eager?o:i>l.from))&&(n[r]={get:t,from:i,eager:!!o})},u=[];return"default"===t&&(l("@xyflow/react","12.10.2",()=>Promise.all([E.e(905),E.e(638)]).then(()=>()=>E(905))),l("flowbook","0.1.2",()=>Promise.all([E.e(951),E.e(638),E.e(873)]).then(()=>()=>E(873)))),e[t]=u.length?Promise.all(u).then(()=>e[t]=1):1}}})(),(()=>{var e;E.g.importScripts&&(e=E.g.location+"");var r=E.g.document;if(!e&&r&&(r.currentScript&&"SCRIPT"===r.currentScript.tagName.toUpperCase()&&(e=r.currentScript.src),!e)){var t=r.getElementsByTagName("script");if(t.length)for(var o=t.length-1;o>-1&&(!e||!/^http(s?):/.test(e));)e=t[o--].src}if(!e)throw new Error("Automatic publicPath is not supported in this browser");e=e.replace(/^blob:/,"").replace(/#.*$/,"").replace(/\?.*$/,"").replace(/\/[^\/]+$/,"/"),E.p=e})(),t=e=>{var r=e=>e.split(".").map(e=>+e==e?+e:e),t=/^([^-+]+)?(?:-([^+]+))?(?:\+(.+))?$/.exec(e),o=t[1]?r(t[1]):[];return t[2]&&(o.length++,o.push.apply(o,r(t[2]))),t[3]&&(o.push([]),o.push.apply(o,r(t[3]))),o},o=(e,r)=>{e=t(e),r=t(r);for(var o=0;;){if(o>=e.length)return o<r.length&&"u"!=(typeof r[o])[0];var n=e[o],a=(typeof n)[0];if(o>=r.length)return"u"==a;var i=r[o],l=(typeof i)[0];if(a!=l)return"o"==a&&"n"==l||"s"==l||"u"==a;if("o"!=a&&"u"!=a&&n!=i)return n<i;o++}},n=e=>{var r=e[0],t="";if(1===e.length)return"*";if(r+.5){t+=0==r?">=":-1==r?"<":1==r?"^":2==r?"~":r>0?"=":"!=";for(var o=1,a=1;a<e.length;a++)o--,t+="u"==(typeof(l=e[a]))[0]?"-":(o>0?".":"")+(o=2,l);return t}var i=[];for(a=1;a<e.length;a++){var l=e[a];i.push(0===l?"not("+u()+")":1===l?"("+u()+" || "+u()+")":2===l?i.pop()+" "+i.pop():n(l))}return u();function u(){return i.pop().replace(/^\((.+)\)$/,"$1")}},a=(e,r)=>{if(0 in e){r=t(r);var o=e[0],n=o<0;n&&(o=-o-1);for(var i=0,l=1,u=!0;;l++,i++){var f,s,d=l<e.length?(typeof e[l])[0]:"";if(i>=r.length||"o"==(s=(typeof(f=r[i]))[0]))return!u||("u"==d?l>o&&!n:""==d!=n);if("u"==s){if(!u||"u"!=d)return!1}else if(u)if(d==s)if(l<=o){if(f!=e[l])return!1}else{if(n?f>e[l]:f<e[l])return!1;f!=e[l]&&(u=!1)}else if("s"!=d&&"n"!=d){if(n||l<=o)return!1;u=!1,l--}else{if(l<=o||s<d!=n)return!1;u=!1}else"s"!=d&&"n"!=d&&(u=!1,l--)}}var c=[],p=c.pop.bind(c);for(i=1;i<e.length;i++){var h=e[i];c.push(1==h?p()|p():2==h?p()&p():h?a(h,r):!p())}return!!p()},i=(e,r)=>e&&E.o(e,r),l=e=>(e.loaded=1,e.get()),u=e=>Object.keys(e).reduce((r,t)=>(e[t].eager&&(r[t]=e[t]),r),{}),f=(e,r,t,n)=>{var i=n?u(e[r]):e[r];return(r=Object.keys(i).reduce((e,r)=>!a(t,r)||e&&!o(e,r)?e:r,0))&&i[r]},s=(e,r,t)=>{var n=t?u(e[r]):e[r];return Object.keys(n).reduce((e,r)=>!e||!n[e].loaded&&o(e,r)?r:e,0)},d=(e,r,t,o)=>"Unsatisfied version "+t+" from "+(t&&e[r][t].from)+" of shared singleton module "+r+" (required "+n(o)+")",c=(e,r,t,o,a)=>{var i=e[t];return"No satisfying version ("+n(o)+")"+(a?" for eager consumption":"")+" of shared module "+t+" found in shared scope "+r+".\nAvailable versions: "+Object.keys(i).map(e=>e+" from "+i[e].from).join(", ")},p=e=>{throw new Error(e)},h=e=>{"undefined"!=typeof console&&console.warn&&console.warn(e)},b=(e,r,t)=>t?t():((e,r)=>p("Shared module "+r+" doesn't exist in shared scope "+e))(e,r),m=(v=e=>function(r,t,o,n,a){var i=E.I(r);return i&&i.then&&!o?i.then(e.bind(e,r,E.S[r],t,!1,n,a)):e(r,E.S[r],t,o,n,a)})((e,r,t,o,n,a)=>{if(!i(r,t))return b(e,t,a);var u=f(r,t,n,o);return u?l(u):a?a():void p(c(r,e,t,n,o))}),g=v((e,r,t,o,n,u)=>{if(!i(r,t))return b(e,t,u);var f=s(r,t,o);return a(n,f)||h(d(r,t,f,n)),l(r[t][f])}),y={},w={345:()=>g("default","react",!1,[1,18,2,0]),628:()=>g("default","react-dom",!1,[1,18,2,0]),247:()=>g("default","@lumino/widgets",!1,[1,2,3,1,,"alpha",1]),395:()=>m("default","@xyflow/react",!1,[1,12,10,1],()=>E.e(905).then(()=>()=>E(905))),516:()=>g("default","@jupyterlab/apputils",!1,[1,4,6,8]),562:()=>g("default","@jupyterlab/ui-components",!1,[1,4,5,8]),602:()=>g("default","@lumino/signaling",!1,[1,2,0,0]),830:()=>g("default","@jupyterlab/notebook",!1,[1,4,5,8]),892:()=>g("default","@jupyterlab/services",!1,[1,7,5,8])},k={638:[345,628],873:[247,395,516,562,602,830,892]},S={},E.f.consumes=(e,r)=>{E.o(k,e)&&k[e].forEach(e=>{if(E.o(y,e))return r.push(y[e]);if(!S[e]){var t=r=>{y[e]=0,E.m[e]=t=>{delete E.c[e],t.exports=r()}};S[e]=!0;var o=r=>{delete y[e],E.m[e]=t=>{throw delete E.c[e],r}};try{var n=w[e]();n.then?r.push(y[e]=n.then(t).catch(o)):t(n)}catch(e){o(e)}}})},(()=>{var e={952:0};E.f.j=(r,t)=>{var o=E.o(e,r)?e[r]:void 0;if(0!==o)if(o)t.push(o[2]);else if(638!=r){var n=new Promise((t,n)=>o=e[r]=[t,n]);t.push(o[2]=n);var a=E.p+E.u(r),i=new Error;E.l(a,t=>{if(E.o(e,r)&&(0!==(o=e[r])&&(e[r]=void 0),o)){var n=t&&("load"===t.type?"missing":t.type),a=t&&t.target&&t.target.src;i.message="Loading chunk "+r+" failed.\n("+n+": "+a+")",i.name="ChunkLoadError",i.type=n,i.request=a,o[1](i)}},"chunk-"+r,r)}else e[r]=0};var r=(r,t)=>{var o,n,[a,i,l]=t,u=0;if(a.some(r=>0!==e[r])){for(o in i)E.o(i,o)&&(E.m[o]=i[o]);l&&l(E)}for(r&&r(t);u<a.length;u++)n=a[u],E.o(e,n)&&e[n]&&e[n][0](),e[n]=0},t=self.webpackChunkflowbook=self.webpackChunkflowbook||[];t.forEach(r.bind(null,0)),t.push=r.bind(null,t.push.bind(t))})(),E.nc=void 0;var x=E(935);(_JUPYTERLAB=void 0===_JUPYTERLAB?{}:_JUPYTERLAB).flowbook=x})();
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flowbook-python
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: Reproducibility enforcement for Jupyter notebooks: a JupyterLab extension that enforces rerun consistency by tracking variable- and column-level dependencies between cells.
5
5
  Project-URL: Homepage, https://github.com/stephenfreund/FlowBook
6
6
  Project-URL: Bug Tracker, https://github.com/stephenfreund/FlowBook/issues
@@ -97,7 +97,7 @@ Description-Content-Type: text/markdown
97
97
  **Reproducibility enforcement for Jupyter notebooks.**
98
98
 
99
99
  FlowBook is a JupyterLab extension that enforces _rerun consistency_:
100
- re-executing each cell from the current state would produce a result
100
+ re-executing any cell from the current state would produce a result
101
101
  consistent with a top-to-bottom execution of the notebook,
102
102
  regardless of which cells have been run, modified, and rerun.
103
103
  Cells whose inputs may have changed are marked _stale_,
@@ -108,6 +108,9 @@ When every cell is _clean_ — executed and rerun consistent —
108
108
  the notebook is guaranteed reproducible: running it top-to-bottom from
109
109
  an empty store yields exactly the outputs currently recorded.
110
110
 
111
+ For technical details and a complete evaluation, see our arXiv paper: [FlowBook: Enforcing Reproducibility in Computational Notebooks
112
+ ](https://arxiv.org/abs/2605.01560).
113
+
111
114
  ## Quick Start
112
115
 
113
116
  Install FlowBook using `pip`:
@@ -1,6 +1,6 @@
1
1
  flowbook/__init__.py,sha256=zNgknOhDjL5J82yiQ6LJxpZcnHLl7B_PndouThTRt_4,3052
2
2
  flowbook/__main__.py,sha256=d90JE7Gz6DyjkQ-dwiU3duFA_Kzf2hKKNQJuNI8EHpM,1053
3
- flowbook/_version.py,sha256=ac3tgjoOzsODOlacRPCwz99LbZv0NK7ZYAaBWulFo7E,171
3
+ flowbook/_version.py,sha256=vYZ6rLVUw05xbWIGie5W7cVk18sWwVN72cGSZ2ngSYU,171
4
4
  flowbook/handlers.py,sha256=kT36OTOMcmO7FHjFSHai_nLDiB-PwcnynONAEB3MjRM,774
5
5
  flowbook/kernel_discovery.py,sha256=EXyneUXHTkMu2_otjudRS3WG300GRFBbiIadolG9ogo,3635
6
6
  flowbook/baseline_kernel/__init__.py,sha256=z3dSM5AdJ8n0IaroWSYRY32Uhc-3AxlE-Eq-7cH_ehA,817
@@ -42,13 +42,13 @@ flowbook/kernel/access_events.py,sha256=CSrgGa7jbJUnB06ibfmNnppNGSaAilHon_i4nXQE
42
42
  flowbook/kernel/change_detector.py,sha256=lnnkjUqCm5u6PNPvEUBFALmCK90lrW-IYwxdZD8BNKQ,17634
43
43
  flowbook/kernel/changes.py,sha256=YKKQ_29OdLv-j2SFIzTN2Ym2PV0G4Wb8StJgE9tIBqc,7346
44
44
  flowbook/kernel/flowbook_client.py,sha256=4xgs57HEETHOeH9yiNYdjfLAMCg70D7vZv-fd9dCfLA,2396
45
- flowbook/kernel/flowbook_kernel.py,sha256=mQA_-HTYJXxaAL_B1pxWsDqmJ_BExhgabWv5EcbRGUU,79550
45
+ flowbook/kernel/flowbook_kernel.py,sha256=29e66thKX94uRs4AGJZ_JlelxdiekgMu5WxYcjkkvzo,83669
46
46
  flowbook/kernel/loc_ids.py,sha256=oNAu03yvA_5GefB5z0S0mXjbqf-zWdYZFnTu8VhzJf4,6215
47
47
  flowbook/kernel/locations.py,sha256=Y68sxUkL7tGBmNT3zSFDQbMAtXp0Y3hczMQbNDE3PpE,29732
48
48
  flowbook/kernel/models.py,sha256=j8WGBsuopdN1j0pmdypkpGe926waNpw5JyrnmcMbiO8,14268
49
- flowbook/kernel/notebook_state.py,sha256=qmF7ZCIJdLMhmu7sq214GyCD4sqo9mWPKBGR5UDACB4,26004
50
- flowbook/kernel/protocol.py,sha256=ObpNj5JZLjqFwoUxnmamzDEGBLuHCAOtIE_wQO3jm70,6702
51
- flowbook/kernel/reproducibility_enforcer.py,sha256=x2RamjV_LPdGqz4Awf2ZHGlPQDKnrKm7PcrjjSa2msg,121342
49
+ flowbook/kernel/notebook_state.py,sha256=sIispCoLHQgCH1lFcN2oKBaxNx-OA-DXOGhKhz-17go,27140
50
+ flowbook/kernel/protocol.py,sha256=CATA84--K--yo6Bz6LClag-ueyMVDr-XL6dHru1nYZs,6994
51
+ flowbook/kernel/reproducibility_enforcer.py,sha256=tepxmSsDX8gc6GO8EFJibRhWGUcFlSIH3cl1mahyszo,122556
52
52
  flowbook/kernel/kernelspec/kernel.json,sha256=a5n7ZxmD_4XY3jWQAroPN03ayT7AeUZNjS8MGUviG8U,181
53
53
  flowbook/kernel/kernelspec/logo-32x32.png,sha256=Pv-uhxm8eL001GREpTXqQJ5wS-VGDd4V-yAtZFvWV0M,2273
54
54
  flowbook/kernel/kernelspec/logo-64x64.png,sha256=P6sxTssjbTg4O8kU3wmups583z0sU_1ALesRhritHCY,6495
@@ -70,6 +70,7 @@ flowbook/kernel/tests/test_litmus.py,sha256=qGGpgTEbJ80CfFvTuPj-Sp7O5JpYZMaMDqhv
70
70
  flowbook/kernel/tests/test_loc_ids.py,sha256=T6o00bRTpfjgcczMXUr4WAHNNDncdReI4LfqtWi81c0,16406
71
71
  flowbook/kernel/tests/test_locations.py,sha256=rCs0VTMpo6E4FQnHQTPtlkxCU5TqFGms163xUnZ7zi0,30740
72
72
  flowbook/kernel/tests/test_locset_integration.py,sha256=DpRR66leQBJlkXBiIw-92NQ-Ajw7LlOt7gmJMv8PVVI,31243
73
+ flowbook/kernel/tests/test_meaningful_edit.py,sha256=B3osU6-hhJSDz15o2BWzfyjFGkItnj408OuRtJNci-g,9290
73
74
  flowbook/kernel/tests/test_measure_rerun_overhead.py,sha256=guTjvSBd0-nHf3k13Gg7F3-uot5YAVpzmWjKeffHdxs,8310
74
75
  flowbook/kernel/tests/test_notebook_state.py,sha256=QuBGDI7Cb73-6EQj3-v4MLySexuBlb9nSWoniWkW0ac,41484
75
76
  flowbook/kernel/tests/test_optimizations.py,sha256=IERPWPqG9Z98isBsuLcenlREY2vFejBH1R5rZyPx1U0,16927
@@ -200,7 +201,7 @@ flowbook/kernel_support/tests/test_virtual_fs.py,sha256=vRTtdG-0Hn56iF0-cItKN4a9
200
201
  flowbook/mcp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
201
202
  flowbook/mcp/jupyter_config.py,sha256=lI7ddMrQF3APHr33py2j36jNa--GFblRf_M8CRHCEDw,3252
202
203
  flowbook/mcp/server.py,sha256=Sywz5bH-Si0t98ndT2w4GeKosCnE-UHw5TwtU7YpZs0,29502
203
- flowbook/mcp/session.py,sha256=U2yqhQdW7dnxtyZ5Afea4u6a5fyeWmsOg0NEjMf9ecI,57037
204
+ flowbook/mcp/session.py,sha256=eFrrsVFSOBQ-fG_ba8TzcoXD0WyGnrO-viZEQXLYX0Q,58214
204
205
  flowbook/mcp/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
205
206
  flowbook/mcp/tests/test_cell_validation.py,sha256=uE3f12bxKE9r2SJoxw3L_LWE67C33oaT9wfw11Mw44E,1924
206
207
  flowbook/mcp/tests/test_contents_api.py,sha256=ue_b9l-hlT-0PBtH1-ejq8SMBxlSzWhzFk8sOSBbrRM,16625
@@ -293,22 +294,22 @@ flowbook/util/tests/test_gpu_memory.py,sha256=bXsmiVwQ7RNAea4cHjgMzrJvFIk4Og3Fyv
293
294
  flowbook/util/tests/test_liveness.py,sha256=nnkpLKs1fP_qzSI8rfwWz4ADi8G89JVC21pNLfkDvns,23733
294
295
  flowbook/util/tests/test_notebook_analysis.py,sha256=KJoNd0YCM6tS50FLY8z-hxo2XuBGt4rhb04-lZsDNNM,15793
295
296
  flowbook/util/tests/test_notebook_to_python.py,sha256=LQj1j7s2HQ-YViC-8PVhlbBWUdmcazIKYPKp1r77CUg,11855
296
- flowbook_python-0.1.0.data/data/etc/jupyter/jupyter_server_config.d/flowbook.json,sha256=SBWUy6yIzlYxOUoH8b7g8LpbVFA8riSsI24a49D75pQ,83
297
- flowbook_python-0.1.0.data/data/share/jupyter/labextensions/flowbook/package.json,sha256=OmBrTF982xwyiL3uZ_G3KpZlEBww8mQ8K3pUHvdp11k,6813
298
- flowbook_python-0.1.0.data/data/share/jupyter/labextensions/flowbook/schemas/flowbook/package.json.orig,sha256=CSoWc8riblAon1o4IT3Mie8AcLWFLVEfa8wRXDASOfM,7761
299
- flowbook_python-0.1.0.data/data/share/jupyter/labextensions/flowbook/schemas/flowbook/plugin.json,sha256=gyFTNNZO3TS3IUf2ag4qEp0MhHJdIIJW83fSDBNlAyQ,374
300
- flowbook_python-0.1.0.data/data/share/jupyter/labextensions/flowbook/static/728.b1df4bca1a3305d0d0a7.js,sha256=sd9LyhozBdDQpztInmq2HI1Bz5huwIqZ4GKzYi_MhuQ,10788
301
- flowbook_python-0.1.0.data/data/share/jupyter/labextensions/flowbook/static/873.3ca7ae352f965bccc339.js,sha256=PKeuNS-WW8zDObGP6cXHDtjzV50czr1D4IFDUF4rVdY,51110
302
- flowbook_python-0.1.0.data/data/share/jupyter/labextensions/flowbook/static/905.94c2bfb401597cc2a103.js,sha256=kE4f1HMB4ZBv4xB39qZJYsQMObKEOoDZ8tUqih_5lmc,185362
303
- flowbook_python-0.1.0.data/data/share/jupyter/labextensions/flowbook/static/905.94c2bfb401597cc2a103.js.LICENSE.txt,sha256=4IQjmq-Rys7RAELeOr57IxiUlcHYzf8E29jgR73coC4,781
304
- flowbook_python-0.1.0.data/data/share/jupyter/labextensions/flowbook/static/951.ba84389925d6a0676e79.js,sha256=uoQ4mSXWoGdueau47-_g6oLgVyeg-wK0-eZk6uFYKcg,23080
305
- flowbook_python-0.1.0.data/data/share/jupyter/labextensions/flowbook/static/remoteEntry.97e370ce33befeb5451b.js,sha256=l-NwzjO-_rVFGyfTa_szblyCVRk85dkSSi_46YZZuL0,7814
306
- flowbook_python-0.1.0.data/data/share/jupyter/labextensions/flowbook/static/style.js,sha256=u-pYuJa5_OIRLxiyYEueJa-VX5iYpdwH__UT7GgbxHY,151
307
- flowbook_python-0.1.0.data/data/share/jupyter/labextensions/flowbook/static/third-party-licenses.json,sha256=SMb6b-Owv3hnXj-G5s_QwC0bh3hofMgnEqWgXUS50w4,19656
308
- flowbook_python-0.1.0.data/data/share/jupyter/nbi_extensions/flowbook/extension.json,sha256=YEDOYVN9pn6U-W_Lu1L_NT9OyJgkF8N8QvjeOECOwkU,59
309
- flowbook_python-0.1.0.data/data/share/jupyter/labextensions/flowbook/install.json,sha256=Fv3Z6UBNFAzHSNYXCzXPLFEIg9QAYGspssc9ZQ-SkYY,191
310
- flowbook_python-0.1.0.dist-info/METADATA,sha256=sXVhmzp9PWgsxSGcdIGLzQAxGDPnK2E37q-299apFpE,10292
311
- flowbook_python-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
312
- flowbook_python-0.1.0.dist-info/entry_points.txt,sha256=exIG8mzfGijcgNChot4kJQwfLTGVqOjoizjvkr1Ntn0,601
313
- flowbook_python-0.1.0.dist-info/licenses/LICENSE,sha256=vzQ5CiRavPNATuWPTc47miSh54LsYzpmUTyej6JVDd4,1522
314
- flowbook_python-0.1.0.dist-info/RECORD,,
297
+ flowbook_python-0.1.2.data/data/etc/jupyter/jupyter_server_config.d/flowbook.json,sha256=SBWUy6yIzlYxOUoH8b7g8LpbVFA8riSsI24a49D75pQ,83
298
+ flowbook_python-0.1.2.data/data/share/jupyter/labextensions/flowbook/package.json,sha256=ncXCj3BNYZqEu85H0lSgdqE8GOmkut15gSyxDXrjoOg,6813
299
+ flowbook_python-0.1.2.data/data/share/jupyter/labextensions/flowbook/schemas/flowbook/package.json.orig,sha256=eRm_LCwxkBarB8GQQqkZC37jm3E0jCAiNLNbcT06pMk,7761
300
+ flowbook_python-0.1.2.data/data/share/jupyter/labextensions/flowbook/schemas/flowbook/plugin.json,sha256=gyFTNNZO3TS3IUf2ag4qEp0MhHJdIIJW83fSDBNlAyQ,374
301
+ flowbook_python-0.1.2.data/data/share/jupyter/labextensions/flowbook/static/728.b1df4bca1a3305d0d0a7.js,sha256=sd9LyhozBdDQpztInmq2HI1Bz5huwIqZ4GKzYi_MhuQ,10788
302
+ flowbook_python-0.1.2.data/data/share/jupyter/labextensions/flowbook/static/873.3edec525c5c79ec55cd6.js,sha256=Pt7FJcXHnsVc1snJcZIEqH4WVwoLijEvRhfckg-Cxus,51178
303
+ flowbook_python-0.1.2.data/data/share/jupyter/labextensions/flowbook/static/905.94c2bfb401597cc2a103.js,sha256=kE4f1HMB4ZBv4xB39qZJYsQMObKEOoDZ8tUqih_5lmc,185362
304
+ flowbook_python-0.1.2.data/data/share/jupyter/labextensions/flowbook/static/905.94c2bfb401597cc2a103.js.LICENSE.txt,sha256=4IQjmq-Rys7RAELeOr57IxiUlcHYzf8E29jgR73coC4,781
305
+ flowbook_python-0.1.2.data/data/share/jupyter/labextensions/flowbook/static/951.ba84389925d6a0676e79.js,sha256=uoQ4mSXWoGdueau47-_g6oLgVyeg-wK0-eZk6uFYKcg,23080
306
+ flowbook_python-0.1.2.data/data/share/jupyter/labextensions/flowbook/static/remoteEntry.9c89c62fdbe6db112e2d.js,sha256=nInGL9vm2xEuLXuz7QzmBy8NIgFfnvw33XCCulASiRc,7814
307
+ flowbook_python-0.1.2.data/data/share/jupyter/labextensions/flowbook/static/style.js,sha256=u-pYuJa5_OIRLxiyYEueJa-VX5iYpdwH__UT7GgbxHY,151
308
+ flowbook_python-0.1.2.data/data/share/jupyter/labextensions/flowbook/static/third-party-licenses.json,sha256=SMb6b-Owv3hnXj-G5s_QwC0bh3hofMgnEqWgXUS50w4,19656
309
+ flowbook_python-0.1.2.data/data/share/jupyter/nbi_extensions/flowbook/extension.json,sha256=YEDOYVN9pn6U-W_Lu1L_NT9OyJgkF8N8QvjeOECOwkU,59
310
+ flowbook_python-0.1.2.data/data/share/jupyter/labextensions/flowbook/install.json,sha256=Fv3Z6UBNFAzHSNYXCzXPLFEIg9QAYGspssc9ZQ-SkYY,191
311
+ flowbook_python-0.1.2.dist-info/METADATA,sha256=Xrv82nZiFJXAyODDudncqO42vs_UoY3A1dTlWVS7GKc,10463
312
+ flowbook_python-0.1.2.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
313
+ flowbook_python-0.1.2.dist-info/entry_points.txt,sha256=exIG8mzfGijcgNChot4kJQwfLTGVqOjoizjvkr1Ntn0,601
314
+ flowbook_python-0.1.2.dist-info/licenses/LICENSE,sha256=vzQ5CiRavPNATuWPTc47miSh54LsYzpmUTyej6JVDd4,1522
315
+ flowbook_python-0.1.2.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.29.0
2
+ Generator: hatchling 1.30.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any