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
7 changes: 7 additions & 0 deletions .verification/settings-read-and-sidebar/delivery.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
SettingsReader now reads ObjectState.fields. FieldProxy follows the existing ParameterStructure, resolving one leaf rather than reconstructing every dataclass during paint. ObjectState #67 merged as d34d75803d3b7e65128bf43ab867bb70f37cd013 and this package declares that version.

Properties are a panel in the existing ThreadSidebar. Session, channel and file views inherit the same bar resource; the separate ObjectSidebar and mounting marker are deleted. The right-click editor and that panel still edit the same ObjectConfiguration. Lazy inheritance, explicit overrides, reset and persistence remain owned there.

The user's existing settings actually contain an object sidebar placement. Its value declaration is retained solely to decode that saved data; a nominal ActiveSidebarPlacement parent derives the two current roles, so the old role cannot claim screen space. No configuration file was written.

Executed small native controls: real Toad settings follow registered global edits, retain an explicit child override, and inherit again on reset; padding resolves as a leaf. All three view classes import and are concrete. A first inheritance attempt omitted the application's registry registration and failed setup; corrected application lifecycle passed. A later control reached every assertion for active roles, inherited bar methods and current saved-layout decoding, printed its result, then exceeded the 15-second outer limit during process exit (exit 124). This is not a passing process-exit check. No suite, profile or CPU-speed claim. Physical sidebar trial and live CPU effect follow installation.
122 changes: 122 additions & 0 deletions .verification/settings-read-and-sidebar/finish.plan.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
{
"stages": [
{
"recipes": [
{
"recipe_id": "one-view-bar-and-preserved-stored-layouts",
"operations": [
{
"operation": "patch_target",
"file_path": "/home/ts/wt/codex-live--P181--toad/src/toad/settings_scope.py",
"target_qualname": "ObjectSidebarHost",
"replacements": [
{
"old_source": "class ObjectSidebarHost(Widget):\n \"\"\"The native body in which a viewed object's properties are placed.\"\"\"",
"new_source": ""
}
]
},
{
"operation": "patch_target",
"file_path": "/home/ts/wt/codex-live--P181--toad/src/toad/screens/session_view.py",
"target_qualname": "SidebarSessionView",
"replacements": [
{
"old_source": " @cached_property\n def thread_sidebar(self) -> SidebarT:\n return self.make_thread_sidebar()\n\n def sidebars(self) -> tuple[SideBar, ...]:\n return (self.thread_sidebar,)",
"new_source": ""
}
]
},
{
"operation": "insert_before_target",
"file_path": "/home/ts/wt/codex-live--P181--toad/src/toad/sidebar_placement.py",
"target_qualname": "ChannelsSidebarPlacement",
"source": "class ActiveSidebarPlacement(SidebarPlacement, declared_name=None):\n \"\"\"The sidebar roles a current view can actually own.\"\"\""
},
{
"operation": "patch_target",
"file_path": "/home/ts/wt/codex-live--P181--toad/src/toad/sidebar_placement.py",
"target_qualname": "ChannelsSidebarPlacement",
"replacements": [
{
"old_source": "class ChannelsSidebarPlacement(SidebarPlacement):",
"new_source": "class ChannelsSidebarPlacement(ActiveSidebarPlacement):"
}
]
},
{
"operation": "patch_target",
"file_path": "/home/ts/wt/codex-live--P181--toad/src/toad/sidebar_placement.py",
"target_qualname": "ThreadSidebarPlacement",
"replacements": [
{
"old_source": "class ThreadSidebarPlacement(SidebarPlacement):",
"new_source": "class ThreadSidebarPlacement(ActiveSidebarPlacement):"
}
]
},
{
"operation": "patch_target",
"file_path": "/home/ts/wt/codex-live--P181--toad/src/toad/sidebar_placement.py",
"target_qualname": "ObjectSidebarPlacement",
"replacements": [
{
"old_source": "class ObjectSidebarPlacement(ThreadSidebarPlacement):\n \"\"\"Properties follow the viewed object, beside its other panels.\"\"\"\n\n @classmethod\n def heading(cls, viewed: Iterable[str]) -> str:\n return f\"{super().heading(viewed)} properties\"\n",
"new_source": "class ObjectSidebarPlacement(SidebarPlacement):\n \"\"\"Read already-saved third-bar positions; no current view owns this role.\"\"\""
}
]
},
{
"operation": "patch_target",
"file_path": "/home/ts/wt/codex-live--P181--toad/src/toad/sidebar_layout.py",
"target_qualname": "SidebarLayout.placements",
"replacements": [
{
"old_source": "SidebarPlacement.members_with(SidebarPlacement)",
"new_source": "ActiveSidebarPlacement.members_with(ActiveSidebarPlacement)"
}
]
},
{
"operation": "ensure_import",
"file_path": "/home/ts/wt/codex-live--P181--toad/src/toad/sidebar_layout.py",
"import_source": "from toad.sidebar_placement import ActiveSidebarPlacement"
}
]
}
]
},
{
"recipes": [
{
"recipe_id": "remove-third-bar-mount-imports",
"operations": [
{
"operation": "remove_import_bindings",
"file_path": "/home/ts/wt/codex-live--P181--toad/src/toad/configured_view.py",
"bound_names": [
"ObjectSidebarHost",
"ObjectSidebarPlacement",
"LazyThreadSidebar"
]
},
{
"operation": "remove_import_bindings",
"file_path": "/home/ts/wt/codex-live--P181--toad/src/toad/screens/session_view.py",
"bound_names": [
"ObjectSidebarHost"
]
},
{
"operation": "remove_import_bindings",
"file_path": "/home/ts/wt/codex-live--P181--toad/src/toad/settings_scope.py",
"bound_names": [
"Widget"
]
}
]
}
]
}
]
}
119 changes: 119 additions & 0 deletions .verification/settings-read-and-sidebar/fix.plan.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
{
"recipes": [
{
"recipe_id": "read-leaves-and-reuse-view-sidebar",
"operations": [
{
"operation": "patch_target",
"file_path": "/home/ts/wt/codex-live--P181--toad/src/toad/settings_scope.py",
"target_qualname": "SettingsReader.settings",
"replacements": [
{
"old_source": "return self.configuration_state.to_resolved_object()",
"new_source": "return self.configuration_state.fields"
}
]
},
{
"operation": "patch_target",
"file_path": "/home/ts/wt/codex-live--P181--toad/src/toad/settings_scope.py",
"target_qualname": "ViewSettingsScope",
"replacements": [
{
"old_source": " \"\"\"A file, thread or channel owns its display overrides.\"\"\"",
"new_source": " \"\"\"A file, thread or channel owns its display overrides.\"\"\"\n\n @property\n @abstractmethod\n def configuration_panel(self): ..."
}
]
},
{
"operation": "patch_target",
"file_path": "/home/ts/wt/codex-live--P181--toad/src/toad/configured_view.py",
"target_qualname": "ObjectSidebar",
"replacements": [
{
"old_source": "class ObjectSidebar(LazyThreadSidebar):\n placement_type = ObjectSidebarPlacement\n\n def __init__(self, configuration):\n self.configuration = configuration\n super().__init__(state=SidebarState())\n\n def make_panels(self):\n return (SideBar.Panel(\"Properties\", ObjectProperties.whole(self.configuration), flex=True),)",
"new_source": ""
}
]
},
{
"operation": "patch_target",
"file_path": "/home/ts/wt/codex-live--P181--toad/src/toad/configured_view.py",
"target_qualname": "ConfiguredView",
"replacements": [
{
"old_source": " @cached_property\n def configuration_sidebar(self):\n return ObjectSidebar(self.configuration_state)\n\n def sidebars(self):\n return (*super().sidebars(), self.configuration_sidebar)\n\n @on(events.Mount)\n async def mount_configuration(self):\n await self.query_one(ObjectSidebarHost).mount(self.configuration_sidebar)",
"new_source": " @property\n def configuration_panel(self):\n return SideBar.Panel(\"Properties\", ObjectProperties.whole(self.configuration_state),\n flex=True, collapsed=True)\n\n def make_thread_sidebar(self):\n return ThreadSidebar(state=SidebarState())\n\n @cached_property\n def thread_sidebar(self):\n return self.make_thread_sidebar()\n\n def sidebars(self):\n return (self.thread_sidebar,)"
}
]
},
{
"operation": "patch_target",
"file_path": "/home/ts/wt/codex-live--P181--toad/src/toad/widgets/side_bar.py",
"target_qualname": "SideBar._compose_panels",
"replacements": [
{
"old_source": "for panel in self.panels:",
"new_source": "for panel in self.panel_entries():"
}
]
},
{
"operation": "insert_before_target",
"file_path": "/home/ts/wt/codex-live--P181--toad/src/toad/widgets/side_bar.py",
"target_qualname": "SideBar._compose_panels",
"source": " def panel_entries(self):\n return iter(self.panels)"
},
{
"operation": "insert_before_target",
"file_path": "/home/ts/wt/codex-live--P181--toad/src/toad/widgets/side_bar.py",
"target_qualname": "ThreadSidebar.__init__",
"source": " def panel_entries(self):\n yield from super().panel_entries()\n yield self.query_ancestor(ViewSettingsScope).configuration_panel"
},
{
"operation": "patch_target",
"file_path": "/home/ts/wt/codex-live--P181--toad/src/toad/screens/session_view.py",
"target_qualname": "SidebarSessionView.sidebars",
"replacements": [
{
"old_source": "return (self.thread_sidebar, self.configuration_sidebar)",
"new_source": "return (self.thread_sidebar,)"
}
]
},
{
"operation": "patch_target",
"file_path": "/home/ts/wt/codex-live--P181--toad/src/toad/screens/session_view.py",
"target_qualname": "SessionBody",
"replacements": [
{
"old_source": "class SessionBody(WidgetView, ToadNode, ObjectSidebarHost, Center):",
"new_source": "class SessionBody(WidgetView, ToadNode, Center):"
}
]
},
{
"operation": "patch_target",
"file_path": "/home/ts/wt/codex-live--P181--toad/src/toad/screens/file_preview.py",
"target_qualname": "FilePreviewScreen.compose",
"replacements": [
{
"old_source": "with SessionBody():\n with FileContents():",
"new_source": "with SessionBody():\n yield self.thread_sidebar\n with FileContents():"
}
]
},
{
"operation": "ensure_import",
"file_path": "/home/ts/wt/codex-live--P181--toad/src/toad/widgets/side_bar.py",
"import_source": "from toad.settings_scope import ViewSettingsScope"
},
{
"operation": "ensure_import",
"file_path": "/home/ts/wt/codex-live--P181--toad/src/toad/configured_view.py",
"import_source": "from toad.widgets.side_bar import ThreadSidebar"
}
]
}
]
}
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ textual = { git = "https://github.com/OpenHCSDev/textual.git", branch = "main" }
formbridge = { git = "https://github.com/OpenHCSDev/formbridge.git", branch = "main" }
textual-image = { git = "https://github.com/OpenHCSDev/textual-image.git", branch = "main" }
textual-diff-view = { git = "https://github.com/OpenHCSDev/textual-diff-view.git", branch = "main" }
objectstate = { git = "https://github.com/OpenHCSDev/objectstate.git", rev = "226fcd8918e16f6d1b594cac0bdcf8057aab832a" }
objectstate = { git = "https://github.com/OpenHCSDev/objectstate.git", rev = "d34d75803d3b7e65128bf43ab867bb70f37cd013" }

[project.scripts]
toad = "toad.cli:main"
Expand Down
31 changes: 13 additions & 18 deletions src/toad/configured_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,17 @@
from toad.screens.modal import DismissibleScreen, ModalPanel
from toad.forms.presentation import FormModal
from toad.session_tracker import SidebarState
from toad.settings_scope import ObjectSidebarHost, ViewSettingsScope
from toad.sidebar_placement import ObjectSidebarPlacement
from toad.settings_scope import ViewSettingsScope
from toad.widgets.acting_button import DismissButton
from toad.widgets.comms_menu import ActionMenu
from toad.widgets.side_bar import (
LazyThreadSidebar,
SideBar,
SidebarPanelWidget,
ThreadSidebar,
)


class ObjectSidebar(LazyThreadSidebar):
placement_type = ObjectSidebarPlacement

def __init__(self, configuration):
self.configuration = configuration
super().__init__(state=SidebarState())

def make_panels(self):
return (SideBar.Panel("Properties", ObjectProperties.whole(self.configuration), flex=True),)


class ObjectProperties(ObjectFormView, SidebarPanelWidget):
Expand Down Expand Up @@ -55,16 +46,20 @@ def configuration_state(self) -> ObjectConfiguration:
return ObjectConfiguration(identity, self.app.saved_settings.state,
self.app.saved_settings.path().parent, self)

@property
def configuration_panel(self):
return SideBar.Panel("Properties", ObjectProperties.whole(self.configuration_state),
flex=True, collapsed=True)

def make_thread_sidebar(self):
return ThreadSidebar(state=SidebarState())

@cached_property
def configuration_sidebar(self):
return ObjectSidebar(self.configuration_state)
def thread_sidebar(self):
return self.make_thread_sidebar()

def sidebars(self):
return (*super().sidebars(), self.configuration_sidebar)

@on(events.Mount)
async def mount_configuration(self):
await self.query_one(ObjectSidebarHost).mount(self.configuration_sidebar)
return (self.thread_sidebar,)

@on(events.Unmount)
def close_configuration(self):
Expand Down
1 change: 1 addition & 0 deletions src/toad/screens/file_preview.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,6 @@ def project_root(self) -> Path:

def compose(self) -> ComposeResult:
with SessionBody():
yield self.thread_sidebar
with FileContents():
yield self.preview
9 changes: 2 additions & 7 deletions src/toad/screens/session_view.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from textual.color import Color
from textual.layout import Layout
from toad.configured_view import ConfiguredView
from toad.settings_scope import ConversationSettingsScope, ObjectSidebarHost
from toad.settings_scope import ConversationSettingsScope
from ui_theme.scheme import BackgroundColour
from textual.reactive import reactive
from contextlib import contextmanager
Expand Down Expand Up @@ -175,7 +175,7 @@ def retained_native_presentations(self):
return ()


class SessionBody(WidgetView, ToadNode, ObjectSidebarHost, Center):
class SessionBody(WidgetView, ToadNode, Center):
"""The shared sidebar/content layout reads its owner directly."""

width = height = FILL
Expand Down Expand Up @@ -223,9 +223,4 @@ def _thread_sidebar_state(self) -> SidebarState:
@abstractmethod
def make_thread_sidebar(self) -> SidebarT: ...

@cached_property
def thread_sidebar(self) -> SidebarT:
return self.make_thread_sidebar()

def sidebars(self) -> tuple[SideBar, ...]:
return (self.thread_sidebar, self.configuration_sidebar)
10 changes: 6 additions & 4 deletions src/toad/settings_scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from abc import abstractmethod
from objectstate import ObjectState
from textual.dom import DOMNode
from textual.widget import Widget


class SettingsReader(DOMNode):
Expand All @@ -17,7 +16,7 @@ def configuration_state(self) -> ObjectState:

@property
def settings(self):
return self.configuration_state.to_resolved_object()
return self.configuration_state.fields


class SettingsScope(SettingsReader):
Expand All @@ -32,10 +31,13 @@ def settings_widgets(self, widget_type):
class ViewSettingsScope(SettingsScope):
"""A file, thread or channel owns its display overrides."""

@property
@abstractmethod
def configuration_panel(self): ...


class ConversationSettingsScope(ViewSettingsScope):
"""A thread or channel additionally owns conversation and composer settings."""


class ObjectSidebarHost(Widget):
"""The native body in which a viewed object's properties are placed."""

Loading
Loading