238 lines
14 KiB
Markdown
238 lines
14 KiB
Markdown
# 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:
|
||
|
||
```sh
|
||
.venv/bin/ruff check --ignore I001 path/to/changed_file.py
|
||
```
|
||
|
||
## Project structure and dependency direction
|
||
|
||
Relevant GUI structure:
|
||
|
||
```text
|
||
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, main `QUndoStack`, modified state, and high-level operations.
|
||
- `commands/`: `QUndoCommand` implementations. Persistent changes to the document model go through commands.
|
||
- `controllers/`: connect actions and widgets to document/service operations. Keep workflow logic out of `MainWindow`.
|
||
- `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 from `bedit_gui`.
|
||
|
||
Preferred direction:
|
||
|
||
```text
|
||
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:
|
||
|
||
```text
|
||
src/bedit_gui/ui/forms/
|
||
```
|
||
|
||
Generated Python is in:
|
||
|
||
```text
|
||
src/bedit_gui/ui/generated/
|
||
```
|
||
|
||
Never add handwritten behavior to generated UI modules. Change the `.ui` form and regenerate with:
|
||
|
||
```sh
|
||
.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 `Document` facade 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`: system `QClipboard` and 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`, and `GraphComponentLabel` live in `bedit_gui.models` and serialize through `to_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 `multiplicity` is 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 `Document` methods and graph-specific `QUndoCommand` classes. Incremental document signals must also update the editor’s cached `Graph`; 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 of `views/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
|
||
|
||
- `ApplicationSettings` is the typed `QSettings` facade for BEdit preferences. The current settings include log level, graph snap-to-grid size, and ordered library paths under `libraries/paths`.
|
||
- The Settings dialog edits library paths locally until OK is accepted. It supports BEdit `.bedit.json`/`.json` and `.beb` files, 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.py` is the composition root for the separate `bedit-sim` Qt application.
|
||
- Keep compilation GUI-independent. Shared compile entry points belong in `bedit_simulation` and must not create or depend on a `QApplication` or window.
|
||
- The simulator opens BEdit `.beb`/`.json` documents or serialized simulation `.bes`/`.json` files.
|
||
- A component selector uses default simulation settings. A stored simulation-settings block already identifies its component; do not require both selectors.
|
||
- `SimulationDatabase.active_simulation` is the persisted active settings ID selected when the Simulation Settings dialog is accepted.
|
||
- Simulation files serialize `bedit_gui.simulation_models.SimulationRoot`. JSON uses the `root_type = "simulation_root"` discriminator; binary `.bes` files use their own BES header.
|
||
- `SimulationRoot` contains 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 `.bes` file to that process.
|
||
- Keep simulator file workflows in their own services/controllers rather than adding them to `MainWindow` or 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`/`undesired` state is applied to the open document with an undoable command.
|
||
- OpenModelica compilation runs `checkModel(...)` before `buildModel(...)`; the equation/variable summary flows through `ModelBuildResult.output` and is shown in the application log.
|
||
|
||
### Causality inference caveats
|
||
|
||
- `_CausalityEngine.inference()` clears every flattened bond’s causality to `NONE` before inference; it does not continue from saved causalities. It currently does not clear old `undesired` flags.
|
||
- 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 selects `connection.target` in 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:
|
||
|
||
```sh
|
||
.venv/bin/ruff check --ignore I001 path/to/changed_files.py
|
||
```
|
||
|
||
For Qt smoke checks in a headless environment:
|
||
|
||
```sh
|
||
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.
|