haybale-share 0.0.39__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.
@@ -0,0 +1,225 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # The MANIFEST/*.manifest packaging-artifact rules above are unanchored and
36
+ # (on a case-insensitive filesystem) also match our real, tracked
37
+ # haywire_studio/share/manifest/ package — un-ignore it explicitly.
38
+ !packages/haywire-studio/src/haywire_studio/share/manifest/
39
+
40
+ # Installer logs
41
+ pip-log.txt
42
+ pip-delete-this-directory.txt
43
+
44
+ # Unit test / coverage reports
45
+ htmlcov/
46
+ .tox/
47
+ .nox/
48
+ .coverage
49
+ .coverage.*
50
+ .cache
51
+ nosetests.xml
52
+ coverage.xml
53
+ *.cover
54
+ *.py,cover
55
+ .hypothesis/
56
+ .pytest_cache/
57
+ cover/
58
+
59
+ # Translations
60
+ *.mo
61
+ *.pot
62
+
63
+ # Django stuff:
64
+ *.log
65
+ local_settings.py
66
+ db.sqlite3
67
+ db.sqlite3-journal
68
+
69
+ # Flask stuff:
70
+ instance/
71
+ .webassets-cache
72
+
73
+ # Scrapy stuff:
74
+ .scrapy
75
+
76
+ # Sphinx documentation
77
+ docs/_build/
78
+
79
+ # PyBuilder
80
+ .pybuilder/
81
+ target/
82
+
83
+ # Jupyter Notebook
84
+ .ipynb_checkpoints
85
+
86
+ # IPython
87
+ profile_default/
88
+ ipython_config.py
89
+
90
+ # pyenv
91
+ # For a library or package, you might want to ignore these files since the code is
92
+ # intended to run in multiple environments; otherwise, check them in:
93
+ # .python-version
94
+
95
+ # pipenv
96
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
97
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
98
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
99
+ # install all needed dependencies.
100
+ #Pipfile.lock
101
+
102
+ # UV
103
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
104
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
105
+ # commonly ignored for libraries.
106
+ #uv.lock
107
+
108
+ # poetry
109
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
110
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
111
+ # commonly ignored for libraries.
112
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
113
+ #poetry.lock
114
+
115
+ # pdm
116
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
117
+ #pdm.lock
118
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
119
+ # in version control.
120
+ # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
121
+ .pdm.toml
122
+ .pdm-python
123
+ .pdm-build/
124
+
125
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
126
+ __pypackages__/
127
+
128
+ # Celery stuff
129
+ celerybeat-schedule
130
+ celerybeat.pid
131
+
132
+ # SageMath parsed files
133
+ *.sage.py
134
+
135
+ # Environments
136
+ **/.env
137
+ .venv
138
+ env/
139
+ venv/
140
+ ENV/
141
+ env.bak/
142
+ venv.bak/
143
+
144
+ # Spyder project settings
145
+ .spyderproject
146
+ .spyproject
147
+
148
+ # Rope project settings
149
+ .ropeproject
150
+
151
+ # mkdocs documentation
152
+ /site
153
+
154
+ # mypy
155
+ .mypy_cache/
156
+ .dmypy.json
157
+ dmypy.json
158
+
159
+ # Pyre type checker
160
+ .pyre/
161
+
162
+ # pytype static type analyzer
163
+ .pytype/
164
+
165
+ # Cython debug symbols
166
+ cython_debug/
167
+
168
+ # PyCharm
169
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
170
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
171
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
172
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
173
+ .idea/
174
+
175
+ # Abstra
176
+ # Abstra is an AI-powered process automation framework.
177
+ # Ignore directories containing user credentials, local state, and settings.
178
+ # Learn more at https://abstra.io/docs
179
+ .abstra/
180
+
181
+ # Visual Studio Code
182
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
183
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
184
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
185
+ # you could uncomment the following to ignore the enitre vscode folder
186
+ .vscode/
187
+
188
+ # Ruff stuff:
189
+ .ruff_cache/
190
+
191
+ # PyPI configuration file
192
+ .pypirc
193
+
194
+ # Cursor
195
+ # Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
196
+ # exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
197
+ # refer to https://docs.cursor.com/context/ignore-files
198
+ .cursorignore
199
+ .cursorindexingignore
200
+
201
+ .DS_Store
202
+ saves/*.*
203
+
204
+ # Dev-mode graphs (local testing only, not part of the repo)
205
+ graphs/*.json
206
+ # MkDocs build output
207
+ site/
208
+
209
+ # Baked docs for the haywire-core wheel (generated by scripts/bake_docs.py;
210
+ # force-included into the wheel at build time). The *contents* are never
211
+ # committed, but the directory itself is kept via a committed .gitkeep so the
212
+ # hatchling force-include target exists in a fresh checkout — otherwise every
213
+ # editable install (`uv sync`) fails the build on a missing force-include path.
214
+ packages/haywire-core/src/haywire/_baked_docs/*
215
+ !packages/haywire-core/src/haywire/_baked_docs/.gitkeep
216
+
217
+ # Local git worktrees (claude-code workflow)
218
+ .worktrees/
219
+
220
+ # Haywire project config (user-local, not repo state)
221
+ .haywire/
222
+
223
+ # Local symlink to external dev project (machine-specific path)
224
+ barn/haybale-visiongraph
225
+
@@ -0,0 +1,8 @@
1
+ Metadata-Version: 2.4
2
+ Name: haybale-share
3
+ Version: 0.0.39
4
+ Summary: Project publishing editor for Haywire
5
+ License: MIT
6
+ Requires-Python: >=3.12
7
+ Requires-Dist: haywire-core>=0.0.39
8
+ Requires-Dist: nicegui>=3.12.1
@@ -0,0 +1,45 @@
1
+ """Project publishing — the Share editor and its flow.
2
+
3
+ Publishing is PROJECT-scoped, not library-scoped (ADR 0023): a haywire project
4
+ is one uv workspace root with one marketstall feed and one git remote, and
5
+ every ``barn/*`` library versions in lockstep. That is why this is its own
6
+ library rather than an editor inside haybale-marketplace, whose job is
7
+ *consuming* feeds — its own architecture doc says it is "not a publisher".
8
+
9
+ The engine lives in :mod:`haywire.core.publishing`; everything here is UI.
10
+ """
11
+
12
+ from importlib.metadata import version as _pkg_version
13
+ from pathlib import Path
14
+
15
+ from haywire.core.library.base import BaseLibrary
16
+ from haywire.core.library.decorator import library
17
+ from haywire.ui.editor.registry import EditorTypeRegistry
18
+
19
+
20
+ @library(
21
+ label="Share",
22
+ id="share",
23
+ version=_pkg_version("haybale-share"),
24
+ description="Publish this project: dependencies, version, docs, commit, tag, push",
25
+ url="",
26
+ help_url="",
27
+ author="",
28
+ author_url="",
29
+ dependencies=[],
30
+ tags=["publishing"],
31
+ file_watcher=True,
32
+ )
33
+ class Library(BaseLibrary):
34
+ """Project publishing library — the Share editor."""
35
+
36
+ def register_components(self):
37
+ base_path = Path(__file__).parent
38
+
39
+ self.add_folder_to_registry(
40
+ folder_path=str(base_path / "editors"),
41
+ registry_cls=EditorTypeRegistry,
42
+ )
43
+
44
+ def validate(self) -> bool:
45
+ return True
File without changes
@@ -0,0 +1,174 @@
1
+ """Runnable panel harness for the Share flow.
2
+
3
+ uv run python -m haybale_share._flow
4
+
5
+ Opens http://localhost:8091 with every screen in every state, side by side
6
+ with the scenario list. Nothing writes: each scenario hand-builds flow state
7
+ and calls the panel directly, never a pipeline step. See ``_harness.py`` for
8
+ why fixtures beat driving a real ``SharePipeline`` here.
9
+
10
+ Boots a real library system so the themes and ``hui`` elements resolve — the
11
+ point is to look at the panels as they will actually appear, not at unstyled
12
+ approximations. Mirrors ``tests/ui/harness/app.py``.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import os
18
+ import sys
19
+ from pathlib import Path
20
+
21
+ from typing import Callable
22
+
23
+ from nicegui import app, ui
24
+
25
+ from haywire.core.di.config import (
26
+ create_library_system_service,
27
+ set_global_injector,
28
+ set_library_system,
29
+ )
30
+ from haywire.core.di.context import set_workspace_root
31
+
32
+ from ._harness import Panel
33
+ from ._state import ShareFlow
34
+
35
+ _HERE = Path(__file__).resolve()
36
+ # _flow/ → haybale_share/ → haybale-share/ → barn/ → repo root
37
+ _REPO_ROOT = _HERE.parent.parent.parent.parent
38
+ _BARN = str(_REPO_ROOT / "barn")
39
+
40
+
41
+ def _render_scenario(container: ui.element, label: str, build, panel) -> None:
42
+ """Render one scenario into *container*, inside the flow's real chrome.
43
+
44
+ The progress bar, warning rows and error banner come from
45
+ ``haywire.ui.components.stepper``, exactly as ``show_step_flow`` composes
46
+ them — a panel judged without its chrome is judged in the wrong context.
47
+ """
48
+ from haywire.ui.components.stepper import render_error, render_progress, render_warnings
49
+
50
+ from .panels import suppress_duplicate_error
51
+
52
+ container.clear()
53
+ flow = build()
54
+
55
+ def _rerender() -> None:
56
+ _render_scenario(container, label, build, panel)
57
+
58
+ with container:
59
+ ui.label(label).classes("text-sm font-medium")
60
+ ui.label(f"step = {flow.step!r}").classes("text-xs font-mono hw-text-muted")
61
+
62
+ # 640px: the width show_share_flow() opens the real popup at.
63
+ #
64
+ # `hw-panel` is not decoration — nearly every Quasar field rule in
65
+ # STATIC_CSS is scoped to it (the field label colour, the underline
66
+ # colours, the focus background). Popup applies it to its own content
67
+ # column, so a harness that omits it renders every select and input
68
+ # with browser defaults and misreports what the panel looks like.
69
+ with (
70
+ ui.column()
71
+ .classes("gap-2 p-3 rounded hw-panel")
72
+ .style("width: 640px; border: 1px solid var(--hw-border); background: var(--hw-bg-elevated);")
73
+ ):
74
+ render_progress(flow)
75
+ render_warnings(flow)
76
+ # The same error_detail show_share_flow passes. Rendering the banner
77
+ # without it is how this harness first showed a duplicate the real
78
+ # flow does not have — a harness that diverges from production
79
+ # reports bugs that are its own.
80
+ render_error(flow, _rerender, suppress_duplicate_error)
81
+ panel(flow, _rerender)
82
+
83
+
84
+ def _inject_theme() -> None:
85
+ """Put the workbench theme's --hw-* variables on :root.
86
+
87
+ Without this every token resolves to empty and the panels render with
88
+ browser defaults — borders vanish, `hw-text-dim` is plain black. The app
89
+ shell normally does this (`shell.py`'s `_theme_css`), but the harness has
90
+ no shell, so it did what a harness must never do: showed the panels
91
+ looking materially different from production, and specifically hid the
92
+ boundaries this screen relies on.
93
+ """
94
+ from haywire.core.di.config import get_library_system
95
+ from haywire.ui.app.shell import STATIC_CSS
96
+
97
+ try:
98
+ registry = get_library_system().get_theme_registry()
99
+ keys = list(registry.list_workbench_keys())
100
+ if not keys:
101
+ return
102
+ theme = registry.get_workbench(keys[0])
103
+ css = " ".join(f"{k}: {v};" for k, v in theme.to_css_vars().items())
104
+ # The page background too: the workbench themes are dark, and a dark
105
+ # panel floating on a white page misrepresents every contrast
106
+ # judgement this harness exists to support.
107
+ body_css = "body { background: var(--hw-bg-page); color: var(--hw-text-body); }"
108
+ ui.add_head_html(f"<style>:root {{ {css} }} {body_css}</style>")
109
+ # The shell's own static CSS, verbatim — it is what styles Quasar
110
+ # fields and (globally, since they portal to <body>) the .q-menu
111
+ # dropdowns. Without it a select opens as a white menu with white
112
+ # text, which is a harness artefact and not something the panel does.
113
+ ui.add_css(STATIC_CSS)
114
+ except Exception as exc: # noqa: BLE001 — a themeless harness still renders
115
+ print(f"harness: could not inject a workbench theme ({exc})")
116
+
117
+
118
+ def _page() -> None:
119
+ from ._harness import SCENARIOS
120
+
121
+ _inject_theme()
122
+ ui.query("body").classes("hw-app")
123
+
124
+ with ui.row().classes("w-full h-screen gap-0 items-stretch"):
125
+ with (
126
+ ui.column()
127
+ .classes("gap-1 p-3 h-full overflow-auto")
128
+ .style("width: 300px; border-right: 1px solid var(--hw-border);")
129
+ ):
130
+ ui.label("Share flow — panels").classes("text-sm font-medium")
131
+ ui.label("Fixture state. Nothing writes.").classes("text-xs hw-text-muted")
132
+ buttons: list[tuple[ui.button, str, Callable[[], ShareFlow], Panel]] = []
133
+
134
+ with ui.scroll_area().classes("flex-1 w-full"):
135
+ for group, entries in SCENARIOS.items():
136
+ ui.label(group).classes("text-xs hw-text-dim mt-2")
137
+ for label, build, panel in entries:
138
+ button = (
139
+ ui.button(label).props("flat dense align=left no-caps").classes("w-full text-xs")
140
+ )
141
+ buttons.append((button, label, build, panel))
142
+
143
+ with ui.scroll_area().classes("flex-1 h-full"):
144
+ stage = ui.column().classes("p-4 gap-2")
145
+
146
+ for button, label, build, panel in buttons:
147
+ button.on_click(lambda _e=None, la=label, b=build, p=panel: _render_scenario(stage, la, b, p))
148
+
149
+ first_label, first_build, first_panel = next(iter(SCENARIOS.values()))[0]
150
+ _render_scenario(stage, first_label, first_build, first_panel)
151
+
152
+
153
+ def main() -> None:
154
+ library_paths = [_BARN] if os.path.isdir(_BARN) else []
155
+ service = create_library_system_service(
156
+ workspace_root=str(_REPO_ROOT),
157
+ library_paths=library_paths,
158
+ enable_file_watching=False,
159
+ watch_settings=False,
160
+ )
161
+ set_library_system(service)
162
+ set_global_injector(service.injector)
163
+ set_workspace_root(str(_REPO_ROOT))
164
+
165
+ ui.page("/")(_page)
166
+ app.on_shutdown(lambda: service.cleanup() if hasattr(service, "cleanup") else None)
167
+
168
+ ui.run(port=8091, show=False, title="Share flow — panel harness", reload=False)
169
+
170
+
171
+ if __name__ in {"__main__", "__mp_main__"}:
172
+ if str(_REPO_ROOT) not in sys.path:
173
+ sys.path.insert(0, str(_REPO_ROOT))
174
+ main()