Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 5 additions & 10 deletions ccflow/tests/ui/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,15 @@ def test_parser_composition(self):

# Viewer-specific
assert hasattr(args, "browser_width")
assert hasattr(args, "browser_height")
assert hasattr(args, "viewer_width")
assert hasattr(args, "title")

def test_viewer_layout_defaults(self):
"""Test default values for viewer-specific arguments."""
parser = _get_ui_args_parser()
args = parser.parse_args([])

assert args.browser_width == 400
assert args.browser_height == 700
assert args.viewer_width is None
assert args.title == "ccflow Model Registry"

def test_viewer_layout_custom_values(self):
"""Test setting custom values for viewer layout arguments."""
Expand All @@ -47,16 +45,13 @@ def test_viewer_layout_custom_values(self):
[
"--browser-width",
"500",
"--browser-height",
"800",
"--viewer-width",
"600",
"--title",
"My Registry",
]
)

assert args.browser_width == 500
assert args.browser_height == 800
assert args.viewer_width == 600
assert args.title == "My Registry"

def test_overrides_positional(self):
"""Test overrides are captured as positional arguments."""
Expand Down
55 changes: 15 additions & 40 deletions ccflow/tests/ui/test_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,26 +246,31 @@ def test_init_returns_viewable(self):
panel = viewer.__panel__()
assert isinstance(panel, pn.viewable.Viewable)

def test_panel_is_row_layout(self):
"""Test that the panel is a Row layout (browser + viewer side by side)."""
def test_panel_is_page_layout(self):
"""Test that the panel is a pmui.Page (viewport-filling layout with resizable sidebar)."""
import panel_material_ui as pmui

registry = ModelRegistry(name="test")
viewer = ModelRegistryViewer(registry)
panel = viewer.__panel__()
assert isinstance(panel, pn.Row)
assert isinstance(panel, pmui.Page)
assert panel.sidebar_width == viewer.browser_width
assert panel.title == viewer.title

def test_init_with_custom_dimensions(self):
"""Test initialization with custom width/height."""
"""Test initialization with custom sidebar width and title."""
registry = ModelRegistry(name="test")
viewer = ModelRegistryViewer(
registry,
browser_width=500,
browser_height=800,
viewer_width=600,
title="My Registry",
)

assert viewer.browser_width == 500
assert viewer.browser_height == 800
assert viewer.viewer_width == 600
assert viewer.title == "My Registry"
panel = viewer.__panel__()
assert panel.sidebar_width == 500
assert panel.title == "My Registry"

def test_browser_viewer_wiring(self):
"""Test that browser selection updates viewer."""
Expand All @@ -282,42 +287,12 @@ def test_browser_viewer_wiring(self):
assert viewer._viewer.model == model

def test_default_browser_dimensions(self):
"""Test default browser dimensions."""
"""Test default browser width and title."""
registry = ModelRegistry(name="test")
viewer = ModelRegistryViewer(registry)

assert viewer.browser_width == 400
assert viewer.browser_height == 700
assert viewer.viewer_width is None

def test_make_browser_column(self):
"""Test _make_browser_column creates proper column."""
registry = ModelRegistry(name="test")
viewer = ModelRegistryViewer(registry)

column = viewer._make_browser_column()
assert isinstance(column, pn.Column)
assert column.width == viewer.browser_width
assert column.height == viewer.browser_height
assert column.scroll is True

def test_make_viewer_column_with_width(self):
"""Test _make_viewer_column with specified width."""
registry = ModelRegistry(name="test")
viewer = ModelRegistryViewer(registry, viewer_width=600)

column = viewer._make_viewer_column()
assert isinstance(column, pn.Column)
assert column.width == 600

def test_make_viewer_column_without_width(self):
"""Test _make_viewer_column without specified width (stretch)."""
registry = ModelRegistry(name="test")
viewer = ModelRegistryViewer(registry)

column = viewer._make_viewer_column()
assert isinstance(column, pn.Column)
assert column.sizing_mode == "stretch_width"
assert viewer.title == "ccflow Model Registry"

def test_model_param_default_none(self):
"""Test that model param starts as None."""
Expand Down
19 changes: 6 additions & 13 deletions ccflow/ui/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,13 @@ def _get_ui_args_parser() -> argparse.ArgumentParser:
"--browser-width",
type=int,
default=400,
help="Width of the registry browser panel (default: 400)",
help="Initial width of the registry browser sidebar (default: 400). User can drag to resize.",
)
parser.add_argument(
"--browser-height",
type=int,
default=700,
help="Height of the registry browser panel (default: 700)",
)
parser.add_argument(
"--viewer-width",
type=int,
default=None,
help="Fixed width for model viewer panel (default: stretch)",
"--title",
type=str,
default="ccflow Model Registry",
help="Title shown in the page header (default: 'ccflow Model Registry')",
)

return parser
Expand Down Expand Up @@ -91,8 +85,7 @@ def create_app():
viewer = ModelRegistryViewer(
registry,
browser_width=args.browser_width,
browser_height=args.browser_height,
viewer_width=args.viewer_width,
title=args.title,
)
return viewer.__panel__()

Expand Down
17 changes: 11 additions & 6 deletions ccflow/ui/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@ class ModelTypeViewer(param.Parameterized):
def __init__(self, **params):
super().__init__(**params)

self._pane = pn.pane.HTML("", width=1200)
self._pane = pn.pane.HTML("", sizing_mode="stretch_width")
self._layout = pn.Column(
self._pane,
sizing_mode="stretch_width",
)

self.param.watch(self._on_type_change, "model_type")
Expand Down Expand Up @@ -79,7 +80,7 @@ def _on_type_change(self, event):
name_html = f'<code style="{_FIELD_STYLES["name"]}">{html.escape(name)}</code>'
type_html = f'<code style="{_FIELD_STYLES["type"]}">{html.escape(field_type)}</code>'
desc_html = f' — <span style="{_FIELD_STYLES["description"]}">{html.escape(desc)}</span>' if desc else ""
field_items.append(f"<li>{name_html} ({type_html}){desc_html}</li>")
field_items.append(f'<li style="overflow-wrap:anywhere;">{name_html} ({type_html}){desc_html}</li>')

fields_html = ""
if field_items:
Expand All @@ -96,7 +97,7 @@ def _on_type_change(self, event):
<div>
<div style="margin-bottom:6px;">
<span style="font-weight:600;">Type:</span>
<code style="{_FIELD_STYLES["type"]}">{html.escape(type_name)}</code>
<code style="{_FIELD_STYLES["type"]}overflow-wrap:anywhere;">{html.escape(type_name)}</code>
</div>
{docs_html}
{fields_html}
Expand All @@ -115,10 +116,11 @@ def __init__(self, **params):
super().__init__(**params)

self.model_path = ""
self._metadata = pn.pane.HTML("", width=1200)
self._metadata = pn.pane.HTML("", sizing_mode="stretch_width")

self._layout = pn.Column(
self._metadata,
sizing_mode="stretch_width",
)

self.param.watch(self._on_model_change, "model")
Expand All @@ -142,7 +144,7 @@ def _render_dependencies(self, model):
# Unique elements, sorted
rows = sorted(set(all_paths))

items = "".join(f"<li><code>{html.escape(row)}</code></li>" for row in rows)
items = "".join(f'<li><code style="overflow-wrap:anywhere;">{html.escape(row)}</code></li>' for row in rows)

return f"""
<div style="margin-top:8px;">
Expand Down Expand Up @@ -219,20 +221,23 @@ def __init__(self, **params):
value={},
mode="view",
menu=False,
width=600,
sizing_mode="stretch_width",
min_width=400,
)

self._json_container = pn.Column(
"## Parameters",
self._json_editor,
visible=False, # hidden initially
sizing_mode="stretch_width",
)

self._layout = pn.Column(
"## Model Viewer",
self._tabs,
pn.Spacer(height=12),
self._json_container,
sizing_mode="stretch_width",
)

self.param.watch(self._on_model_change, "model")
Expand Down
55 changes: 17 additions & 38 deletions ccflow/ui/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,26 +125,19 @@ def _on_tree_select(self, event):
class ModelRegistryViewer(param.Parameterized):
"""
Top-level viewer that composes the RegistryBrowser and ModelViewer
into a scrollable two-panel layout.
into a viewport-filling page with a resizable sidebar.
"""

# Layout parameters
browser_width = param.Integer(
default=400,
bounds=(200, None),
doc="Width of the registry browser panel (px)",
doc="Initial width of the registry browser sidebar (px). User can drag to resize at runtime.",
)

browser_height = param.Integer(
default=700,
bounds=(300, None),
doc="Height of the registry browser panel (px)",
)

viewer_width = param.Integer(
default=None,
allow_None=True,
doc="Optional fixed width for the model viewer panel (px)",
title = param.String(
default="ccflow Model Registry",
doc="Title shown in the page header.",
)

model = param.Parameter(
Expand Down Expand Up @@ -172,33 +165,19 @@ def _on_selection(e):

self._browser.param.watch(_on_selection, "selected_model")

# Build layout
self._layout = pn.Row(
self._make_browser_column(),
self._make_viewer_column(),
# Wrap browser in a scrolling Column so large registries remain navigable.
sidebar_panel = pn.Column(
self._browser,
sizing_mode="stretch_both",
scroll=True,
)

def __panel__(self):
return self._layout

# Internal helpers

def _make_browser_column(self):
return pn.Column(
self._browser,
width=self.browser_width,
height=self.browser_height,
scroll=True, # ✅ only left panel scrolls
self._layout = pmui.Page(
sidebar=[sidebar_panel],
main=[self._viewer],
sidebar_width=self.browser_width,
title=self.title,
)

def _make_viewer_column(self):
if self.viewer_width is not None:
return pn.Column(
self._viewer,
width=self.viewer_width,
)
else:
return pn.Column(
self._viewer,
sizing_mode="stretch_width",
)
def __panel__(self):
return self._layout
Loading