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
132 changes: 132 additions & 0 deletions .verification/view-sidebar-discovery/author.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import json
from pathlib import Path
root=Path.cwd(); ops=[]
def patch(f,t,o,n): ops.append(dict(operation='patch_target',file_path=str(root/f),target_qualname=t,replacements=[dict(old_source=o,new_source=n)]))
def insert(f,t,s): ops.append(dict(operation='insert_before_target',file_path=str(root/f),target_qualname=t,source=s))
def imp(f,s): ops.append(dict(operation='ensure_import',file_path=str(root/f),import_source=s))
f='src/toad/widgets/side_bar.py'
insert(f,'ThreadSidebar','''class PanelPage(FillHeight, containers.Vertical):
"""The original panel and its controls, retained while another entry is selected."""


class PanelPicker(OneRow, FillWidth, widgets.Select):
"""One compact selector over the entries supplied by the viewed object."""


class PanelMenu(FillHeight, containers.Vertical):
"""Discover once from the owner; mount each original panel only when selected."""

def __init__(self, entries, navigation: SidebarState):
super().__init__()
self.entries = tuple(entries)
self.navigation = navigation
selected = next((entry for entry in self.entries
if entry.title == navigation.panel_title), self.entries[0])
self.picker = PanelPicker(((entry.title, entry) for entry in self.entries),
value=selected, allow_blank=False, compact=True)
self.body = PanelPage()
self.pages: dict[SideBar.Panel, PanelPage] = {}

def compose(self):
yield self.picker
yield self.body

@on(events.Mount)
async def open_selection(self):
await self.show_panel(self.picker.value)

@on(widgets.Select.Changed)
async def selection_changed(self, event):
if event.select is self.picker and event.value is not widgets.Select.NULL:
event.stop()
await self.show_panel(event.value)

async def show_panel(self, entry):
for panel, page in self.pages.items():
if page.display and panel is not entry:
panel.widget.finish()
page.display = panel is entry
panel.widget.sidebar_visibility_changed()
if entry not in self.pages:
children = (() if entry.header_control is None else (entry.header_control,))
page = PanelPage(*children, entry.widget, id=entry.id)
self.pages[entry] = page
await self.body.mount(page)
self.navigation.panel_title = entry.title
entry.widget.sidebar_visibility_changed()

def activate(self, widget):
self.picker.value = next(entry for entry in self.entries if entry.widget is widget)

def finish(self):
for entry in self.pages:
entry.widget.finish()


''')
insert(f,'SidebarPanelWidget.sidebar_visibility_changed',''' def finish(self) -> None:
"""Passive panels have no pending edit to submit."""
''')
patch(f,'ThreadSidebar.panel_entries',''' yield self.query_ancestor(ViewSettingsScope).configuration_panel''',''' yield from self.query_ancestor(ViewSettingsScope).configuration_panels''')
insert(f,'ThreadSidebar.__init__',''' def _compose_panels(self):
yield PanelMenu(self.panel_entries(), self.navigation)

def reveal_panel(self, widget):
self.query_one(PanelMenu).activate(widget)
''')
f='src/toad/session_tracker.py'
patch(f,'SidebarState',''' panel_scroll_y: float = 0''',''' panel_scroll_y: float = 0
panel_title: str = ""''')
f='src/toad/settings_scope.py'
patch(f,'ViewSettingsScope.configuration_panel',''' def configuration_panel(self): ...''',''' def configuration_panels(self): ...''')
f='src/toad/forms/form_view.py'
insert(f,'FieldView.of',''' @classmethod
def from_field(cls, field: DeclaredField):
"""The field provider supplies the declaration used to select its presentation."""
declaration = field.declaration
parameter = ParameterInfo.create(
name=declaration.name, param_type=field.annotation,
current_value=field.value, default_value=declaration.default_value,
description=declaration.description, is_required=declaration.is_required)
return cls.showing(type(parameter).__mro__)(field)
''')
patch(f,'FieldView.of','''return [cls.showing(type(parameter).__mro__)(field)''','''return [cls.from_field(field)''')
f='src/toad/configured_view.py'
patch(f,'ObjectProperties','''class ObjectProperties(ObjectFormView, SidebarPanelWidget):
"""The sidebar edits the view's original state directly."""''','''class ObjectProperties(FillHeight, SidebarPanelWidget):
"""One declared settings entry; its form is built when that entry is opened."""

def __init__(self, field):
super().__init__()
self.field = field

def compose(self):
self.form = ObjectFormView(self.field.state, [ObjectFieldView.from_field(self.field)])
yield self.form

def finish(self):
self.form.finish()''')
patch(f,'ObjectConfigurationDialog',''' def __init__(self, state):
super().__init__()
self.form = ObjectFormView.whole(state)''',''' def __init__(self, panels):
super().__init__()
self.form = PanelMenu(panels, SidebarState())''')
patch(f,'ConfiguredView.configuration_panel',''' def configuration_panel(self):
return SideBar.Panel("Properties", ObjectProperties.whole(self.configuration_state),
flex=True, collapsed=True)''',''' def configuration_panels(self):
state = self.configuration_state
for path in state.direct_parameter_paths():
if state.shows(path.value):
field = state.field(path.value)
yield SideBar.Panel(field.label, ObjectProperties(field),
id=f"settings-{path.value}", flex=True)''')
patch(f,'ConfiguredView.open_configuration','''ObjectConfigurationDialog(self.configuration_state)''','''ObjectConfigurationDialog(self.configuration_panels)''')
imp(f,'from toad.native_style import FillHeight')
imp(f,'from toad.forms.object_configuration import ObjectFieldView')
imp(f,'from toad.widgets.side_bar import PanelMenu')
f='src/toad/screens/comms.py'
s=Path(f).read_text(); start=s.index(' def make_thread_sidebar(self)'); end=s.index(' def compose(self)',start)
patch(f,'CommsScreen.make_thread_sidebar',s[start:end].rstrip(),'')
f='src/toad/widgets/session_thread_panels.py'
patch(f,'PlanSessionPanel.update',''' sidebar.query_one(f"#{self.declared_name}-panel", SideBarCollapsible).collapsed = False''',''' sidebar.reveal_panel(plan)''')
Path('.verification/view-sidebar-discovery/move.plan.json').write_text(json.dumps({'recipes':[{'recipe_id':'selected-object-panel-menu','operations':ops}]},indent=2))
15 changes: 15 additions & 0 deletions .verification/view-sidebar-discovery/control.plan.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"recipes": [
{
"recipe_id": "selected-view-dropdown-control",
"operations": [
{
"operation": "insert_before_target",
"file_path": "/home/ts/wt/codex-live--P181--toad/tests/textual/test_sidebar_projection.py",
"target_qualname": "FrameApp",
"source": "async def test_view_menu_discovers_settings_and_keeps_each_objects_state(workspace):\n from toad.configured_view import ObjectProperties\n from toad.forms.object_configuration import ObjectValueView\n from toad.preferences import SETTINGS\n from toad.widgets.side_bar import PanelMenu\n from toad.widgets.thread_comms import ThreadCommsSidebar\n\n workspace.comms.channels.create_tag('settings')\n preview = workspace.project / 'example.txt'\n preview.write_text('A viewed file\\n')\n async with workspace.app(size=(140, 45)) as ui:\n app, pilot = ui.textual\n original = ui.selected_tab\n channel = await ui.open_channel('#settings', actor=workspace.viewer)\n view = app.workspace_sessions.require(channel)\n bar = await ui.revealed(view.thread_sidebar)\n menu = bar.query_one(PanelMenu)\n assert not bar.query(ThreadCommsSidebar)\n assert menu.entries and all(isinstance(entry.widget, ObjectProperties) for entry in menu.entries)\n assert len(menu.pages) == 1\n state = view.configuration_state\n assert state.identity == app.session_navigation.get(channel).identity\n await ui.until(lambda: bool(menu.query(ObjectValueView)), why='selected settings fields')\n assert 'ui.column_width' in {part.field.path.value for part in menu.query(ObjectValueView)}\n assert menu.region.width and menu.query_one(ObjectValueView).region.height\n ui_entry = menu.picker.value\n other = next(entry for entry in menu.entries if entry is not ui_entry)\n menu.picker.value = other\n await ui.until(lambda: menu.navigation.panel_title == other.title, why='dropdown selection')\n menu.picker.value = ui_entry\n await ui.until(lambda: menu.navigation.panel_title == ui_entry.title, why='returning to settings')\n app.saved_settings.set(SETTINGS.ui.column_width, 83)\n assert state.fields.ui.column_width == 83\n state.field('ui.column_width').edit(91)\n app.saved_settings.set(SETTINGS.ui.column_width, 85)\n assert state.fields.ui.column_width == 91\n state.reset_parameter('ui.column_width')\n assert state.fields.ui.column_width == 85\n file_mode = await ui.open_preview(preview)\n file_view = app.workspace_sessions.require(file_mode)\n file_bar = await ui.revealed(file_view.thread_sidebar)\n file_menu = file_bar.query_one(PanelMenu)\n assert file_view.configuration_state.identity == app.session_navigation.get(file_mode).identity\n assert file_view.configuration_state is not state\n assert not file_bar.query(ThreadCommsSidebar)\n assert len(file_menu.entries) < len(menu.entries)\n await ui.select_tab(channel)\n assert view.thread_sidebar is bar and bar.query_one(PanelMenu) is menu\n assert menu.picker.value is ui_entry\n await ui.select_tab(original)\n\n\n"
}
]
}
]
}
58 changes: 58 additions & 0 deletions .verification/view-sidebar-discovery/control_author.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import json
from pathlib import Path
p=Path('tests/textual/test_sidebar_projection.py').resolve()
s='''async def test_view_menu_discovers_settings_and_keeps_each_objects_state(workspace):
from toad.configured_view import ObjectProperties
from toad.forms.object_configuration import ObjectValueView
from toad.preferences import SETTINGS
from toad.widgets.side_bar import PanelMenu
from toad.widgets.thread_comms import ThreadCommsSidebar

workspace.comms.channels.create_tag('settings')
preview = workspace.project / 'example.txt'
preview.write_text('A viewed file\\n')
async with workspace.app(size=(140, 45)) as ui:
app, pilot = ui.textual
original = ui.selected_tab
channel = await ui.open_channel('#settings', actor=workspace.viewer)
view = app.workspace_sessions.require(channel)
bar = await ui.revealed(view.thread_sidebar)
menu = bar.query_one(PanelMenu)
assert not bar.query(ThreadCommsSidebar)
assert menu.entries and all(isinstance(entry.widget, ObjectProperties) for entry in menu.entries)
assert len(menu.pages) == 1
state = view.configuration_state
assert state.identity == app.session_navigation.get(channel).identity
await ui.until(lambda: bool(menu.query(ObjectValueView)), why='selected settings fields')
assert 'ui.column_width' in {part.field.path.value for part in menu.query(ObjectValueView)}
assert menu.region.width and menu.query_one(ObjectValueView).region.height
ui_entry = menu.picker.value
other = next(entry for entry in menu.entries if entry is not ui_entry)
menu.picker.value = other
await ui.until(lambda: menu.navigation.panel_title == other.title, why='dropdown selection')
menu.picker.value = ui_entry
await ui.until(lambda: menu.navigation.panel_title == ui_entry.title, why='returning to settings')
app.saved_settings.set(SETTINGS.ui.column_width, 83)
assert state.fields.ui.column_width == 83
state.field('ui.column_width').edit(91)
app.saved_settings.set(SETTINGS.ui.column_width, 85)
assert state.fields.ui.column_width == 91
state.reset_parameter('ui.column_width')
assert state.fields.ui.column_width == 85
file_mode = await ui.open_preview(preview)
file_view = app.workspace_sessions.require(file_mode)
file_bar = await ui.revealed(file_view.thread_sidebar)
file_menu = file_bar.query_one(PanelMenu)
assert file_view.configuration_state.identity == app.session_navigation.get(file_mode).identity
assert file_view.configuration_state is not state
assert not file_bar.query(ThreadCommsSidebar)
assert len(file_menu.entries) < len(menu.entries)
await ui.select_tab(channel)
assert view.thread_sidebar is bar and bar.query_one(PanelMenu) is menu
assert menu.picker.value is ui_entry
await ui.select_tab(original)


'''
ops=[dict(operation='insert_before_target',file_path=str(p),target_qualname='FrameApp',source=s)]
Path('.verification/view-sidebar-discovery/control.plan.json').write_text(json.dumps({'recipes':[{'recipe_id':'selected-view-dropdown-control','operations':ops}]},indent=2))
9 changes: 9 additions & 0 deletions .verification/view-sidebar-discovery/delivery.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
The viewed-object bar now has one dropdown. Its settings entries come from that view's admitted dataclass fields; native agent panels join those entries through their existing declared family. No settings roster or third bar is added. The context menu opens the same entries over the same ObjectState.

OpenHCS's ConfigWindow binds each selected config to its original ObjectState and builds its editor lazily. This change follows that ownership: one retained view state inherits the live global defaults, and only the selected form is built. Explicit overrides, reset, persistence and field-source metadata stay with ObjectConfiguration and DeclaredField.

Channel views no longer create Connection/Comms agent panels using the sender's identity. File, channel and native views inherit the same sidebar resource and selection lifecycle. Settings entries retain their actual DottedFieldPath identity, rather than matching display labels. Native role panels retain their nominal widget identity.

Accordion-specific relationship, context, project and plan readers now use an inherited panel-host capability. A dropdown page and a collapsible both own their visibility and reveal behavior. Hidden dropdown pages stop asking for observations. Plan arrival can select its original panel; keyboard focus goes to the view's own control.

One focused actual UI control passed in 14.63s: channel and file selection, declaration discovery, lazy forms, nonempty painted fields, dropdown switching, live default inheritance, explicit override/reset, return-to-view retention, shared context editor state, native relationship-panel activation and hidden-panel visibility. Earlier attempts exposed the shell descriptor limit and a redundant abstract override hiding the inherited sidebar factory; both were corrected. No paid provider, broad suite or profiler was used. The final keyboard focus change was source-traced; no extra run added. Physical reopened Toad trial remains Tristan's.
58 changes: 58 additions & 0 deletions .verification/view-sidebar-discovery/finish.plan.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
{
"recipes": [
{
"recipe_id": "migrate-panel-consumers",
"operations": [
{
"operation": "patch_target",
"file_path": "/home/ts/wt/codex-live--P181--toad/src/toad/forms/form_view.py",
"target_qualname": "FieldView.of",
"replacements": [
{
"old_source": "return [cls.from_field(field)",
"new_source": "return [cls.showing(type(parameter).__mro__)(field)"
}
]
},
{
"operation": "patch_target",
"file_path": "/home/ts/wt/codex-live--P181--toad/src/toad/forms/form_view.py",
"target_qualname": "FieldView.from_field",
"replacements": [
{
"old_source": "current_value=field.value, default_value=declaration.default_value,",
"new_source": "current_value=None, default_value=declaration.default_value,"
}
]
},
{
"operation": "patch_target",
"file_path": "/home/ts/wt/codex-live--P181--toad/src/toad/widgets/session_thread_panels.py",
"target_qualname": "PlanSessionPanel.update",
"replacements": [
{
"old_source": " if plan := sidebar.query_one_optional(Plan):\n plan.entries = entries\n if entries:\n sidebar.reveal_panel(plan)",
"new_source": " if sidebar.panels_loaded:\n plan = next(panel.widget for owner, panel in zip(sidebar._panel_owners, sidebar.panels)\n if owner is self)\n plan.entries = entries\n if entries:\n sidebar.reveal_panel(plan)"
}
]
},
{
"operation": "remove_import_bindings",
"file_path": "/home/ts/wt/codex-live--P181--toad/src/toad/screens/comms.py",
"bound_names": [
"CoordinationStatus",
"RelationshipSort",
"ThreadCommsSidebar"
]
},
{
"operation": "remove_import_bindings",
"file_path": "/home/ts/wt/codex-live--P181--toad/src/toad/widgets/session_thread_panels.py",
"bound_names": [
"SideBarCollapsible"
]
}
]
}
]
}
18 changes: 18 additions & 0 deletions .verification/view-sidebar-discovery/finish_author.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import json
from pathlib import Path
root=Path.cwd(); ops=[]
def p(f,t,o,n): ops.append(dict(operation='patch_target',file_path=str(root/f),target_qualname=t,replacements=[dict(old_source=o,new_source=n)]))
p('src/toad/forms/form_view.py','FieldView.of','return [cls.from_field(field)','return [cls.showing(type(parameter).__mro__)(field)')
p('src/toad/forms/form_view.py','FieldView.from_field','current_value=field.value, default_value=declaration.default_value,','current_value=None, default_value=declaration.default_value,')
p('src/toad/widgets/session_thread_panels.py','PlanSessionPanel.update', ''' if plan := sidebar.query_one_optional(Plan):
plan.entries = entries
if entries:
sidebar.reveal_panel(plan)''',''' if sidebar.panels_loaded:
plan = next(panel.widget for owner, panel in zip(sidebar._panel_owners, sidebar.panels)
if owner is self)
plan.entries = entries
if entries:
sidebar.reveal_panel(plan)''')
for f,names in [('src/toad/screens/comms.py',['CoordinationStatus','RelationshipSort','ThreadCommsSidebar']),('src/toad/widgets/session_thread_panels.py',['SideBarCollapsible'])]:
ops.append(dict(operation='remove_import_bindings',file_path=str(root/f),bound_names=names))
Path('.verification/view-sidebar-discovery/finish.plan.json').write_text(json.dumps({'recipes':[{'recipe_id':'migrate-panel-consumers','operations':ops}]},indent=2))
Loading
Loading