14 KiB
BEdit Agent Guide
This file is the handoff context for coding agents working in this repository. Read it before making changes.
Working agreement
- Make only the changes the user requested.
- Keep completely out of unrelated code. Do not reformat, reorder, rename, clean up, or “improve” code that is outside the task.
- Preserve existing user changes and assume a dirty worktree belongs to the user.
- Inspect the relevant files before deciding on an implementation.
- Prefer small, modular changes over growing
MainWindow, a controller, or another file into a monolith. - When the user asks why something happens, diagnose and explain it without editing files unless they also ask for a fix.
- Do not create commits unless explicitly requested.
Code style
Follow the local style in the file being edited, with these user preferences taking priority:
- Prefer compact, readable one-line imports. Do not introduce parenthesized multiline imports.
- Avoid spreading short function calls, conditions, and expressions across multiple lines.
- Do not run broad formatters or import sorters.
- Do not use a lint autofix over the project.
- Keep classes and functions focused. Extract a controller, service, command, model, or reusable widget when a feature would otherwise make an existing file large.
- Generated modules contain no handwritten application behavior.
The project uses Ruff, but import sorting is intentionally not enforced during scoped checks:
.venv/bin/ruff check --ignore I001 path/to/changed_file.py
Project structure and dependency direction
Relevant GUI structure:
src/bedit_gui/
├── application.py
├── commands/
├── controllers/
├── documents/
├── models.py
├── services/
├── ui/
│ ├── forms/
│ └── generated/
├── resources/
│ └── generated/
├── utils/
└── views/
└── models/
Responsibilities:
application.py: composition root. Create and connect the application, document, window, services, and controllers here.documents/document.py: editable document facade. Owns the core model, path, mainQUndoStack, modified state, and high-level operations.commands/:QUndoCommandimplementations. Persistent changes to the document model go through commands.controllers/: connect actions and widgets to document/service operations. Keep workflow logic out ofMainWindow.views/: handwritten widget/window/graphics behavior.views/models/: Qt item models used by views.services/: non-visual functionality such as files, clipboard, logging, and settings.models.py: GUI metadata persisted inside the core document, including icon, graph, and simulation databases.bedit_core: domain model and serialization. It must never import frombedit_gui.
Preferred direction:
views/controllers
↓
documents/services
↓
commands
↓
bedit_core
Avoid introducing imports from bedit_gui into bedit_core or circular dependencies between services and documents.
Qt Designer and generated files
Raw forms are in:
src/bedit_gui/ui/forms/
Generated Python is in:
src/bedit_gui/ui/generated/
Never add handwritten behavior to generated UI modules. Change the .ui form and regenerate with:
.venv/bin/python scripts/generate_qt_files.py
The generation script also fixes the package-qualified resource import. Calling pyside6-uic directly without the script can produce a broken resources_rc import.
Raw resources and the QRC file live under src/bedit_gui/resources/. Generated resource Python lives under src/bedit_gui/resources/generated/.
Undo and document changes
- The main application document owns the main undo stack.
- The icon editor owns a separate local undo stack.
- The
Documentfacade should expose high-level methods that push commands. Controllers should normally call those methods rather than construct commands. - Commands mutate the model in
redo()/undo()and emit the appropriate document signals. - Multi-object user operations should be one command or one undo macro.
- Allocate stable IDs and final names before pushing a command so redo reproduces the same result.
- Component names must be unique within their destination component dictionary. Conflicts use
_0,_1, and so on. - Copy/paste must generate new component, port, parameter, connection, and icon-shape IDs and rewrite references.
Document tree
DocumentTreeModel has two columns:
- Column 0: editable document/component name.
- Column 1: rendered component icon.
Use selectedRows(0) for multi-selection; selectedIndexes() returns both columns. Filter selected descendants when an ancestor is also selected.
The tree uses extended row selection. Delete, cut, and copy may operate on multiple components. Paste targets either:
- the document root, or
- the component dictionary of a selected graph component.
The controller listens to document model/icon signals and refreshes the tree/icon cache.
Clipboard architecture
Clipboard support is intentionally extensible:
services/clipboard.py: systemQClipboardand MIME/JSON handling.services/component_clipboard.py: component payload serialization and ID remapping.controllers/clipboard_controller.py: focus-based action router and handlers.
ClipboardHandler is the base implementation for editor-specific routing. The document tree and graph editor have component handlers registered in application.py.
Text widgets use their native copy(), cut(), and paste() methods. Component clipboard data uses the custom BEdit MIME type and JSON; never use pickle or live object references.
The graph handler copies/cuts/deletes selected component items and deletes selected connection items. Paste targets the displayed graph and places new components at the mouse position, or at the viewport center when the mouse is outside the canvas.
Icon editor conventions
- Icons are GUI metadata stored in
document.metadata["icon_database"]. - Shapes currently include rectangles, text, and lines.
- Shape changes use the icon editor’s local undo stack.
- Shapes are selectable, movable, resizable, pixel-snapped, and constrained to the icon scene.
- Ports are separate 16×16 items: black for inputs and white for outputs. They can be moved but are not ordinary deletable/copyable shapes.
- Colors are serialized as
#rrggbbaa. - The icon scene is currently
(-64, -64, 128, 128). - The grid spacing is 8 scene pixels and is drawn only inside the scene rectangle.
- Keep reusable widgets such as the RGBA color button independent of the icon editor.
- Static icon previews belong in rendering utilities, not in the interactive editor scene.
Graph editor conventions
- Graph GUI metadata is stored in
document.metadata["graph_database"].GraphDatabase,Graph,GraphConnection, andGraphComponentLabellive inbedit_gui.modelsand serialize throughto_data()/from_data(). - Core graph topology and connections remain in
bedit_core.models; positions, routed points, labels, and other presentation metadata belong in the GUI graph database. - Component positions are absolute scene positions. Connection metadata stores the full point list, including endpoints; only interior points are draggable corner items.
- Component labels are visible by default, italic, and centered below the rendered icon. Their persisted position is relative to the icon’s bottom-center. Label visibility and completed label moves are undoable.
- The graph editor has normal and connection modes. The toolbar actions are exclusive and Space toggles modes while focus is inside the editor.
- Normal mode supports component and label dragging. Connection mode shows icon ports, prevents component/label movement, and uses two component clicks to choose a compatible port pair.
- Signal connections require output-to-input. Signal outputs may fan out; signal inputs accept only one connection. A bond port accepts another connection only when its
multiplicityis true. Bond domains must be compatible. - While choosing a connection, the first component is highlighted and a temporary dashed line follows the mouse. When several port pairs are possible, output-to-input choices are listed first.
- Signal connections render with full arrows. Bond connections render with half arrows and a perpendicular causality tick. Connection endpoints are clipped to rendered icon bounds plus
CONNECTION_BOUNDING_BOX_SPACING. - Components, connections, and connection points are separate graphics items. Connections are selectable/deletable; connection points are draggable and have their own delete context action.
- Persistent canvas edits go through
Documentmethods and graph-specificQUndoCommandclasses. Incremental document signals must also update the editor’s cachedGraph; otherwise rebuilding items during a mode switch can restore stale metadata. render_icon(..., render_ports=True)is the single port-rendering path. Do not duplicate icon or port geometry in the graph editor.- Graph sizing and rendering constants, including
COMPONENT_LABEL_FONT_SIZE, live near the top ofviews/graph_editor_widget.py. - Double-clicking a canvas component selects its document-tree row and opens its graph or equation editor. Canvas component context menus share the document-tree editing actions and add graph-only presentation actions such as Show Label.
Actions and shortcut routing
- Put visible actions in Designer menus/toolbars.
- An action that only exists as a child object may not have an active shortcut; attach it to the relevant widget or place it in a menu/toolbar.
- Route application-wide Copy/Cut/Paste by focused widget through
ClipboardController. - Scope destructive shortcuts to the relevant widget where appropriate.
- Always guard the operation itself even when an action is disabled for presentation.
Application settings
ApplicationSettingsis the typedQSettingsfacade for BEdit preferences. The current settings include log level, graph snap-to-grid size, and ordered library paths underlibraries/paths.- The Settings dialog edits library paths locally until OK is accepted. It supports BEdit
.bedit.json/.jsonand.bebfiles, directories, duplicate suppression, extended selection, and list-focused Delete-key removal. - Add settings behavior in the handwritten dialog/controller/service modules, never in generated UI Python.
Simulator application
bedit_gui/simulation_application.pyis the composition root for the separatebedit-simQt application.- Keep compilation GUI-independent. Shared compile entry points belong in
bedit_simulationand must not create or depend on aQApplicationor window. - The simulator opens BEdit
.beb/.jsondocuments or serialized simulation.bes/.jsonfiles. - A component selector uses default simulation settings. A stored simulation-settings block already identifies its component; do not require both selectors.
SimulationDatabase.active_simulationis the persisted active settings ID selected when the Simulation Settings dialog is accepted.- Simulation files serialize
bedit_gui.simulation_models.SimulationRoot. JSON uses theroot_type = "simulation_root"discriminator; binary.besfiles use their own BES header. SimulationRootcontains source-document identity, the selected component and copied settings, and eventually results. Compiled artifacts are transient application state and must not be serialized; reopen saved simulations by compiling them again. Never serialize live Python objects.- BEdit launches the simulator as a separate process. Compile/Open Simulation integration may transfer a
.besfile to that process. - Keep simulator file workflows in their own services/controllers rather than adding them to
MainWindowor the editor document controller. - Keep compiled-executable launching, time-window progression, result loading, and cancellation inside
bedit_simulation. GUI controllers may schedule backend calls and present state, but must not execute or manage simulation binaries themselves. - The BEdit Compile action performs bond-graph causality inference before Modelica compilation. Inference runs on a deep copy first, then inferred
causality/undesiredstate is applied to the open document with an undoable command. - OpenModelica compilation runs
checkModel(...)beforebuildModel(...); the equation/variable summary flows throughModelBuildResult.outputand is shown in the application log.
Causality inference caveats
_CausalityEngine.inference()clears every flattened bond’s causality toNONEbefore inference; it does not continue from saved causalities. It currently does not clear oldundesiredflags.- Preferred causalities are assigned before all junction constraints are resolved, and the engine has no backtracking to relax a preferred assignment. Some soft-preference conflicts therefore raise a junction error instead of marking a bond undesired.
propagate_to_neighbor()currently selectsconnection.targetin both branches. When propagation starts at a target component, it should traverse to the source; account for this known bug when diagnosing inference behavior.
Verification
The old test suite and its VS Code/packaging references were deliberately removed. Do not recreate a test suite unless asked.
Use checks proportional to the change:
.venv/bin/ruff check --ignore I001 path/to/changed_files.py
For Qt smoke checks in a headless environment:
QT_QPA_PLATFORM=minimal QT_QPA_PLATFORMTHEME= QT_STYLE_OVERRIDE=Fusion .venv/bin/python ...
Prefer focused model, serialization, signal, command undo/redo, and offscreen rendering checks. Do not launch a GUI during verification unless explicitly requested or approved.
Handoff expectations
At the end of a task, report:
- the outcome,
- the files or subsystem changed,
- relevant behavior and limitations,
- checks that actually passed.
Do not claim tests passed when only a lint or smoke check was run.