haybale-haystack 0.3.0__tar.gz → 0.3.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. {haybale_haystack-0.3.0 → haybale_haystack-0.3.2}/.gitignore +3 -1
  2. {haybale_haystack-0.3.0 → haybale_haystack-0.3.2}/PKG-INFO +4 -4
  3. {haybale_haystack-0.3.0 → haybale_haystack-0.3.2}/haybale_haystack/QUICKREF.md +1 -1
  4. {haybale_haystack-0.3.0 → haybale_haystack-0.3.2}/haybale_haystack/editors/haystack_editor.py +94 -1
  5. {haybale_haystack-0.3.0 → haybale_haystack-0.3.2}/haybale_haystack/graph_entry.py +14 -1
  6. {haybale_haystack-0.3.0 → haybale_haystack-0.3.2}/haybale_haystack/haybale.toml +1 -1
  7. haybale_haystack-0.3.2/haybale_haystack/settings/graph_run_settings.py +27 -0
  8. {haybale_haystack-0.3.0 → haybale_haystack-0.3.2}/pyproject.toml +4 -4
  9. haybale_haystack-0.3.0/haybale_haystack/settings/graph_run_settings.py +0 -16
  10. {haybale_haystack-0.3.0 → haybale_haystack-0.3.2}/README.md +0 -0
  11. {haybale_haystack-0.3.0 → haybale_haystack-0.3.2}/haybale_haystack/OVERVIEW.md +0 -0
  12. {haybale_haystack-0.3.0 → haybale_haystack-0.3.2}/haybale_haystack/__init__.py +0 -0
  13. {haybale_haystack-0.3.0 → haybale_haystack-0.3.2}/haybale_haystack/docs/haybale-haystack.editor.HaystackEditor.md +0 -0
  14. {haybale_haystack-0.3.0 → haybale_haystack-0.3.2}/haybale_haystack/docs/haybale-haystack.farmhand.close_graph.md +0 -0
  15. {haybale_haystack-0.3.0 → haybale_haystack-0.3.2}/haybale_haystack/docs/haybale-haystack.farmhand.compile_graph.md +0 -0
  16. {haybale_haystack-0.3.0 → haybale_haystack-0.3.2}/haybale_haystack/docs/haybale-haystack.farmhand.create_graph.md +0 -0
  17. {haybale_haystack-0.3.0 → haybale_haystack-0.3.2}/haybale_haystack/docs/haybale-haystack.farmhand.list_graphs.md +0 -0
  18. {haybale_haystack-0.3.0 → haybale_haystack-0.3.2}/haybale_haystack/docs/haybale-haystack.farmhand.open_graph.md +0 -0
  19. {haybale_haystack-0.3.0 → haybale_haystack-0.3.2}/haybale_haystack/docs/haybale-haystack.farmhand.rename_graph.md +0 -0
  20. {haybale_haystack-0.3.0 → haybale_haystack-0.3.2}/haybale_haystack/docs/haybale-haystack.farmhand.save_graph.md +0 -0
  21. {haybale_haystack-0.3.0 → haybale_haystack-0.3.2}/haybale_haystack/docs/haybale-haystack.farmhand.start_graph.md +0 -0
  22. {haybale_haystack-0.3.0 → haybale_haystack-0.3.2}/haybale_haystack/docs/haybale-haystack.farmhand.stop_graph.md +0 -0
  23. {haybale_haystack-0.3.0 → haybale_haystack-0.3.2}/haybale_haystack/docs/haybale-haystack.panel.EditMacroMenuPanel.md +0 -0
  24. {haybale_haystack-0.3.0 → haybale_haystack-0.3.2}/haybale_haystack/docs/haybale-haystack.panel.GraphRunSettingsPanel.md +0 -0
  25. {haybale_haystack-0.3.0 → haybale_haystack-0.3.2}/haybale_haystack/docs/haybale-haystack.panel.OpenInHaystackMenuPanel.md +0 -0
  26. {haybale_haystack-0.3.0 → haybale_haystack-0.3.2}/haybale_haystack/docs/haybale-haystack.panel.UpdateMacroMenuPanel.md +0 -0
  27. {haybale_haystack-0.3.0 → haybale_haystack-0.3.2}/haybale_haystack/docs/haybale-haystack.setting.HaystackSettings.md +0 -0
  28. {haybale_haystack-0.3.0 → haybale_haystack-0.3.2}/haybale_haystack/docs/haybale-haystack.state.HaystackState.md +0 -0
  29. {haybale_haystack-0.3.0 → haybale_haystack-0.3.2}/haybale_haystack/editors/__init__.py +0 -0
  30. {haybale_haystack-0.3.0 → haybale_haystack-0.3.2}/haybale_haystack/farmhands/__init__.py +0 -0
  31. {haybale_haystack-0.3.0 → haybale_haystack-0.3.2}/haybale_haystack/farmhands/graph_tools.py +0 -0
  32. {haybale_haystack-0.3.0 → haybale_haystack-0.3.2}/haybale_haystack/panels/__init__.py +0 -0
  33. {haybale_haystack-0.3.0 → haybale_haystack-0.3.2}/haybale_haystack/panels/file_browser/menu/file.py +0 -0
  34. {haybale_haystack-0.3.0 → haybale_haystack-0.3.2}/haybale_haystack/panels/graph/menu/macro.py +0 -0
  35. {haybale_haystack-0.3.0 → haybale_haystack-0.3.2}/haybale_haystack/panels/properties/introspect/graph_run_settings_panel.py +0 -0
  36. {haybale_haystack-0.3.0 → haybale_haystack-0.3.2}/haybale_haystack/persistence.py +0 -0
  37. {haybale_haystack-0.3.0 → haybale_haystack-0.3.2}/haybale_haystack/settings/__init__.py +0 -0
  38. {haybale_haystack-0.3.0 → haybale_haystack-0.3.2}/haybale_haystack/settings/haystack_settings.py +0 -0
  39. {haybale_haystack-0.3.0 → haybale_haystack-0.3.2}/haybale_haystack/signals.py +0 -0
  40. {haybale_haystack-0.3.0 → haybale_haystack-0.3.2}/haybale_haystack/state/__init__.py +0 -0
  41. {haybale_haystack-0.3.0 → haybale_haystack-0.3.2}/haybale_haystack/state/haystack_state.py +0 -0
@@ -220,6 +220,8 @@ packages/haywire-core/src/haywire/_baked_docs/*
220
220
  # Haywire project config (user-local, not repo state)
221
221
  .haywire/
222
222
 
223
- # Local symlink to external dev project (machine-specific path)
223
+ # Local symlinks to external dev projects (machine-specific paths)
224
224
  barn/haybale-visiongraph
225
+ barn/haybale-spandrel
225
226
 
227
+ assets/*
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: haybale-haystack
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: File-centric multi-graph manager
5
5
  License: MIT
6
6
  Keywords: graph-management
7
7
  Requires-Python: >=3.12
8
- Requires-Dist: haybale-graph-editor>=0.3.0
9
- Requires-Dist: haybale-studio>=0.3.0
10
- Requires-Dist: haywire-core>=0.3.0
8
+ Requires-Dist: haybale-graph-editor>=0.3.2
9
+ Requires-Dist: haybale-studio>=0.3.2
10
+ Requires-Dist: haywire-core>=0.3.2
11
11
  Requires-Dist: nicegui>=3.18.0
12
12
  Requires-Dist: toml>=0.10.2
@@ -1,4 +1,4 @@
1
- # haybale-haystack — component index (v0.3.0)
1
+ # haybale-haystack — component index (v0.3.2)
2
2
 
3
3
  ## setting
4
4
  - `haybale-haystack:setting:HaystackSettings` — Haystack — Per-workspace settings for haystack scalars.
@@ -8,6 +8,7 @@ from nicegui import ui
8
8
 
9
9
  from haywire.core.access import AccessTier, LevelMode
10
10
  from haywire.ui import elements as hui
11
+ from haywire.ui.components.popup import Popup
11
12
  from haywire.ui.editor import BaseEditor, SlotName, editor
12
13
  from haywire.core.session import (
13
14
  IAppState,
@@ -27,6 +28,7 @@ from haywire.core.signals import (
27
28
  )
28
29
  from haywire.ui.modals import confirm_modal, pick_modal, rename_modal
29
30
 
31
+ from haybale_graph_editor.notices import GraphNotice, GraphNoticesChanged
30
32
  from haybale_graph_editor.state.edit_state import EditState
31
33
  from haybale_graph_editor.state.graph_app_state import GraphAppState
32
34
  from haybale_graph_editor.editors.graph_editor import GraphEditor, definition_path
@@ -75,12 +77,16 @@ class HaystackEditor(BaseEditor):
75
77
  def __init__(self, wrapper):
76
78
  super().__init__(wrapper)
77
79
  self._list_container = None
80
+ self._dismissed_notices: set[tuple[str, str]] = set()
81
+ self._notice_popups: list[Popup] = []
78
82
 
79
83
  # ------------------------------------------------------------------
80
84
  # Signal-bus subscriptions / draw
81
85
  # ------------------------------------------------------------------
82
86
 
83
- @redraw_on(ActiveGraphMoved, GraphDataMutated, GraphUnsavedChanged, HaystackReloaded)
87
+ @redraw_on(
88
+ ActiveGraphMoved, GraphDataMutated, GraphUnsavedChanged, HaystackReloaded, GraphNoticesChanged
89
+ )
84
90
  def _refresh_on_state_move(self, context: "SessionContext", event) -> None:
85
91
  """Empty body — the decorator triggers ``wrapper.redraw()`` after
86
92
  this returns, which re-runs ``draw`` and ``_render_list``."""
@@ -225,6 +231,7 @@ class HaystackEditor(BaseEditor):
225
231
  if self._list_container is None:
226
232
  return
227
233
  self._list_container.clear()
234
+ self._delete_notice_popups()
228
235
 
229
236
  app = context.app
230
237
  if app is None:
@@ -309,6 +316,8 @@ class HaystackEditor(BaseEditor):
309
316
  on_click=lambda eid=eid: self._on_start_execution(eid, context),
310
317
  )
311
318
 
319
+ self._render_notices(eid, context)
320
+
312
321
  # Dirty dot — amber when unsaved, hidden when clean
313
322
  if is_unsaved:
314
323
  ui.element("div").classes("w-2 h-2 rounded-full flex-shrink-0 bg-amber-400").style(
@@ -364,6 +373,89 @@ class HaystackEditor(BaseEditor):
364
373
  on_click=lambda eid=eid: self._on_entry_delete(eid, context),
365
374
  )
366
375
 
376
+ # ------------------------------------------------------------------
377
+ # graph notices
378
+ # ------------------------------------------------------------------
379
+
380
+ def _delete_notice_popups(self) -> None:
381
+ for popup in self._notice_popups:
382
+ popup.delete()
383
+ self._notice_popups.clear()
384
+
385
+ def _visible_notices(self, binding_id: str, context: "SessionContext") -> list[GraphNotice]:
386
+ """Return the notices on a graph that this editor has not dismissed."""
387
+ posted = list(context.app_data[GraphAppState].notices(binding_id))
388
+ return [n for n in posted if (binding_id, n.key) not in self._dismissed_notices]
389
+
390
+ def _render_notices(self, binding_id: str, context: "SessionContext") -> None:
391
+ """Draw the notice icon, tinted warning, and the popup listing each notice and its action."""
392
+ notices = self._visible_notices(binding_id, context)
393
+ if not notices:
394
+ return
395
+ popup = Popup(
396
+ title="Notices", width="26rem", closable=True, backdrop_click_close=True, escape_close=True
397
+ )
398
+ self._notice_popups.append(popup)
399
+ with popup:
400
+ for notice in notices:
401
+ self._render_notice_line(binding_id, notice, popup, context)
402
+ icons = {n.icon for n in notices}
403
+ shared = icons.pop() if len(icons) == 1 else None
404
+ btn = ui.button().props("flat round dense size=xs data-hw-mark=graph-notice")
405
+ with btn:
406
+ # The icon carries the opt-out: the .hw-panel dim rule beats an inline colour.
407
+ ui.icon(shared or hui.icon.notice).classes("hw-use-props-color hw-text-warning")
408
+ btn.tooltip("\n".join(n.text for n in notices))
409
+ btn.on("click.stop", popup.open)
410
+
411
+ def _render_notice_line(
412
+ self, binding_id: str, notice: GraphNotice, popup: Popup, context: "SessionContext"
413
+ ) -> None:
414
+ with (
415
+ ui.column()
416
+ .classes("w-full gap-1 p-2 rounded")
417
+ .style(
418
+ "border-left: 3px solid var(--hw-warning);"
419
+ " background: color-mix(in srgb, var(--hw-warning) 12%, transparent)"
420
+ )
421
+ ):
422
+ with ui.row().classes("w-full items-start gap-2 no-wrap"):
423
+ ui.icon(notice.icon or hui.icon.notice, size="sm").classes(
424
+ "hw-use-props-color hw-text-warning"
425
+ ).props("data-hw-mark=graph-notice-line-icon")
426
+ ui.label(notice.text).classes("flex-1 min-w-0 text-sm")
427
+ if notice.dismissable:
428
+ hui.icon_action(
429
+ hui.icon.close,
430
+ tooltip="Dismiss",
431
+ on_click=lambda k=notice.key: self._dismiss_notice(binding_id, k, context),
432
+ )
433
+ if notice.action is not None and notice.action_label and context.can_access(notice.requires):
434
+ with ui.row().classes("w-full justify-end"):
435
+ hui.button(
436
+ notice.action_label,
437
+ on_click=lambda n=notice: self._run_notice_action(n, popup, context),
438
+ ).props("data-hw-mark=graph-notice-action").classes("w-auto")
439
+
440
+ def _run_notice_action(self, notice: GraphNotice, popup: Popup, context: "SessionContext") -> None:
441
+ """Run a notice's action with this session's context, then close the popup."""
442
+ client = ui.context.client
443
+ popup.close()
444
+ if notice.action is None:
445
+ return
446
+ try:
447
+ notice.action(context)
448
+ except Exception as exc:
449
+ logger.warning(f"HaystackEditor: notice action {notice.key} failed: {exc}")
450
+ with client:
451
+ ui.notify(f"{notice.action_label or 'Action'} failed: {exc}", type="negative")
452
+
453
+ def _dismiss_notice(self, binding_id: str, key: str, context: "SessionContext") -> None:
454
+ """Hide a notice in this editor instance only."""
455
+ self._dismissed_notices.add((binding_id, key))
456
+ # The list redraw deletes the popup (and this button's slot) holding the handler.
457
+ self._render_list(context)
458
+
367
459
  # ------------------------------------------------------------------
368
460
  # per-entry file actions
369
461
  # ------------------------------------------------------------------
@@ -899,4 +991,5 @@ class HaystackEditor(BaseEditor):
899
991
  # ------------------------------------------------------------------
900
992
 
901
993
  def cleanup(self) -> None:
994
+ self._delete_notice_popups()
902
995
  self._list_container = None
@@ -152,7 +152,12 @@ class GraphEntry(GraphContainer):
152
152
  )
153
153
 
154
154
  library_state_container = get_library_state_container()
155
- interpreter = Interpreter(library_state_container=library_state_container)
155
+ interpreter = Interpreter(
156
+ library_state_container=library_state_container,
157
+ exception_bail=lambda: self.run_settings.on_exception_bail,
158
+ # Stopping waits for the runner threads, so it cannot run on the runner that bailed.
159
+ on_bail_run=lambda: self.graph._validation.call_soon(self._stop_after_bail),
160
+ )
156
161
  try:
157
162
  interpreter.load_graph(self.graph)
158
163
  except RuntimeError as exc:
@@ -179,6 +184,14 @@ class GraphEntry(GraphContainer):
179
184
  self.start()
180
185
  return result
181
186
 
187
+ def _stop_after_bail(self) -> None:
188
+ """Stop the run after a worker exception bailed it, and tell every session's haystack UI."""
189
+ if self.haystack is None:
190
+ self.stop_execution()
191
+ return
192
+ self.haystack.stop_execution(self)
193
+ self.haystack._broadcast_data_mutated()
194
+
182
195
  def stop_execution(self) -> None:
183
196
  """Stop execution and shut down the Interpreter."""
184
197
  if not self.is_executing:
@@ -3,7 +3,7 @@
3
3
  # Edited in the studio's library overview — no reinstall, no reload.
4
4
 
5
5
  name = "haybale-haystack"
6
- version = "0.3.0"
6
+ version = "0.3.2"
7
7
  label = "Haystack"
8
8
  description = "File-centric multi-graph manager"
9
9
  tags = ["graph-management"]
@@ -0,0 +1,27 @@
1
+ from haywire.core.settings import setting
2
+ from haywire.core.settings.settings import Settings
3
+ from haywire.barn.builtin.types import BOOL, CHOICES
4
+
5
+
6
+ class GraphRunSettings(Settings):
7
+ """Per-entry run policy. Persisted in the haystack TOML ``[graphs.run]`` table."""
8
+
9
+ autorestart = setting[BOOL](
10
+ False,
11
+ label="Auto-restart",
12
+ description=(
13
+ "Restart this graph automatically after it is stopped by a "
14
+ "structural change that requires recompilation."
15
+ ),
16
+ )
17
+
18
+ on_exception_bail = setting[CHOICES](
19
+ "branch",
20
+ label="On exception, bail",
21
+ description=(
22
+ "How far a run gives up when a node raises: 'branch' ends the current branch "
23
+ "(a loop goes on with its next item), 'frame' ends the current frame, 'run' stops "
24
+ "the graph. The failing node is retried at its next evaluation either way."
25
+ ),
26
+ widget_config={"options": ["branch", "frame", "run"]},
27
+ )
@@ -1,14 +1,14 @@
1
1
  [project]
2
2
  name = "haybale-haystack"
3
- version = "0.3.0"
3
+ version = "0.3.2"
4
4
  description = "File-centric multi-graph manager"
5
5
  requires-python = ">=3.12"
6
6
  license = {text = "MIT"}
7
7
 
8
8
  dependencies = [
9
- "haywire-core>=0.3.0",
10
- "haybale-studio>=0.3.0",
11
- "haybale-graph-editor>=0.3.0",
9
+ "haywire-core>=0.3.2",
10
+ "haybale-studio>=0.3.2",
11
+ "haybale-graph-editor>=0.3.2",
12
12
  "nicegui>=3.18.0",
13
13
  "toml>=0.10.2",
14
14
  ]
@@ -1,16 +0,0 @@
1
- from haywire.core.settings import setting
2
- from haywire.core.settings.settings import Settings
3
- from haywire.barn.builtin.types import BOOL
4
-
5
-
6
- class GraphRunSettings(Settings):
7
- """Per-entry run policy. Persisted in the haystack TOML ``[graphs.run]`` table."""
8
-
9
- autorestart = setting[BOOL](
10
- False,
11
- label="Auto-restart",
12
- description=(
13
- "Restart this graph automatically after it is stopped by a "
14
- "structural change that requires recompilation."
15
- ),
16
- )