mirror of
https://github.com/bancika/diy-layout-creator
synced 2026-11-07 12:24:58 +10:00
agents setup
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
# DIY Layout Creator — repository map
|
||||
|
||||
The guidelines that govern all code changes live in **[`diylc/CLAUDE.md`](diylc/CLAUDE.md)**. Read
|
||||
that file before editing anything under `diylc/`.
|
||||
|
||||
## What is where
|
||||
|
||||
| Path | Contents |
|
||||
| --- | --- |
|
||||
| `diylc/` | The application. Maven reactor with `diylc-core`, `diylc-library`, `diylc-swing`. This is where nearly all work happens. |
|
||||
| `diylc-regression-data/` | Sample `.diy` projects plus reference PNG and netlist outputs used by the regression suite, and the historical reports. |
|
||||
| `diylc-server-api/` | PHP backend for cloud and collaboration features. Runs in an isolated environment on diy-fever.com and cannot be tested locally — do not modify unless explicitly asked. |
|
||||
| `snap/` | Snap packaging metadata. |
|
||||
|
||||
## House rules that apply repository-wide
|
||||
|
||||
- Match the conventions of the surrounding code; consistency beats novelty.
|
||||
- Comment only what the code cannot say for itself. No comments that restate the line below them.
|
||||
- Every new source file begins with the GPL header from `diylc/HEADER.txt`.
|
||||
- Do not commit or push. Leave changes in the working tree for review.
|
||||
|
||||
The project is GPLv3; see `COPYING`.
|
||||
@@ -0,0 +1,170 @@
|
||||
---
|
||||
name: add-component
|
||||
description: Add a new component type to the DIYLC library, or substantially modify an existing one. Use when asked to create a new part (resistor variant, board, jack, switch, pickup, symbol, shape), add editable properties to a component, change how a component draws or reports connectivity, or make a component rotatable/mirrorable.
|
||||
---
|
||||
|
||||
# Adding a component to DIYLC
|
||||
|
||||
Components live in `diylc-library/src/main/java/org/diylc/components/`. They are discovered at
|
||||
runtime by Reflections scanning `org.diylc` — **there is no registry to update**. A class becomes a
|
||||
component by carrying `@ComponentDescriptor` and implementing `IDIYComponent` (in practice, by
|
||||
extending one of the abstract bases).
|
||||
|
||||
## Before writing anything
|
||||
|
||||
1. **Find the closest existing component and read it end to end.** A new axial part follows
|
||||
`passive/Resistor.java`; a new board follows `boards/VeroBoard.java`; a new schematic symbol
|
||||
follows `AbstractSchematicLeadedSymbol` subclasses; a new jack follows `electromechanical/`.
|
||||
The new class should read like a sibling of that file.
|
||||
2. **Ask whether it is really a new class.** If the request is a revision or size variant of
|
||||
something that already exists, prefer adding a `Version` (or similar enum) property to the
|
||||
existing class over creating a parallel class. That keeps old project files loading and keeps the
|
||||
toolbox from sprawling.
|
||||
3. **Pick the right package** — `passive`, `semiconductors`, `electromechanical`, `boards`, `guitar`,
|
||||
`tube`, `chassis`, `shapes`, `smd`, `connectivity`, `misc`, `autocreate`. The package should match
|
||||
the `category` in the descriptor.
|
||||
|
||||
## Choosing a base class
|
||||
|
||||
| Base | Use for |
|
||||
| --- | --- |
|
||||
| `AbstractComponent<T>` | Anything that fits nothing else |
|
||||
| `AbstractLabeledComponent<T>` | Adds name/value label rendering |
|
||||
| `AbstractTransparentComponent<T>` | Anything with an alpha/transparency property |
|
||||
| `AbstractLeadedComponent<T>` | Two-lead axial parts drawn between two points; implement `getDefaultLength()`, `getDefaultWidth()`, `getBodyShape()` |
|
||||
| `AbstractRadialComponent<T>` | Radial (two leads out the same side) parts |
|
||||
| `AbstractCurvedComponent<T>` | Curves and traces defined by control points |
|
||||
| `AbstractAngledComponent<T>` | Parts placed by point and angle |
|
||||
| `AbstractBoard` / `AbstractProtoBoard` / `AbstractVeroBoard` | Boards |
|
||||
| `AbstractMultiPartComponent<T>` | Components made of several independently drawn sections |
|
||||
| `Abstract3LegSymbol`, `AbstractDiodeSymbol`, `AbstractTubeSymbol`, `AbstractGuitarPickup`, `AbstractShape` | The obvious specialisations |
|
||||
|
||||
Extend the most specific base that fits. Do not reimplement behaviour a base already provides.
|
||||
|
||||
## The class
|
||||
|
||||
```java
|
||||
/* ... GPL header copied verbatim from HEADER.txt ... */
|
||||
package org.diylc.components.passive;
|
||||
|
||||
import ... // java.*, then third-party, then org.diylc.*
|
||||
|
||||
@ComponentDescriptor(name = "My Part", author = "Branislav Stojkovic", category = "Passive",
|
||||
creationMethod = CreationMethod.POINT_BY_POINT, instanceNamePrefix = "X",
|
||||
description = "Short sentence shown in the toolbox tooltip", zOrder = IDIYComponent.COMPONENT,
|
||||
transformer = SimpleComponentTransformer.class, enableCache = true)
|
||||
public class MyPart extends AbstractLeadedComponent<Resistance> {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public static Size DEFAULT_WIDTH = new Size(1d / 2, SizeUnit.in);
|
||||
public static Color BODY_COLOR = Color.decode("#82CFFD");
|
||||
|
||||
private Resistance value = null;
|
||||
|
||||
public MyPart() {
|
||||
super();
|
||||
this.bodyColor = BODY_COLOR;
|
||||
}
|
||||
|
||||
@EditableProperty(validatorClass = PositiveMeasureValidator.class)
|
||||
public Resistance getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(Resistance value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawIcon(Graphics2D g2d, int width, int height) { ... }
|
||||
}
|
||||
```
|
||||
|
||||
Requirements the framework enforces:
|
||||
|
||||
- **No-argument public constructor**, and the class must be serializable with a
|
||||
`private static final long serialVersionUID = 1L;`.
|
||||
- `@ComponentDescriptor` needs `name`, `description`, `category`, `author`, `instanceNamePrefix` and
|
||||
`zOrder` at minimum. Set `enableCache = true` for anything whose drawing is expensive and does not
|
||||
depend on neighbouring components.
|
||||
- Appearance constants are `public static` and deliberately **not final**, so they can be overridden
|
||||
at runtime. Follow that.
|
||||
|
||||
## Editable properties
|
||||
|
||||
- Annotate the **getter** with `@EditableProperty`; a matching `setXYZ` must exist. The property name
|
||||
comes from the getter unless you pass `name`.
|
||||
- Use `sortOrder` when the order in the editor matters, `defaultable = false` for properties that
|
||||
must not be remembered as a user default, and `validatorClass` (e.g.
|
||||
`PositiveMeasureValidator`, `PositiveNonZeroMeasureValidator`) where a raw value would be invalid.
|
||||
- Use the measure types from `org.diylc.core.measures` (`Size`, `Resistance`, `Capacitance`,
|
||||
`Voltage`, `Power`, `Percentage`) rather than raw numbers — they carry units and format themselves.
|
||||
- For a runtime-computed list of allowed values, add `@DynamicList(availableValueFunction = "getXyz")`
|
||||
alongside `@EditableProperty`. (The `additionalOptions` example in `EditableProperty`'s own Javadoc
|
||||
is stale — that attribute no longer exists.)
|
||||
- Override `getValueForDisplay()` when the canvas label should combine several properties.
|
||||
|
||||
## Drawing
|
||||
|
||||
`draw(Graphics2D, ComponentState, boolean outlineMode, Project, IDrawingObserver)` runs on every
|
||||
repaint, for every instance. Treat it as a hot path.
|
||||
|
||||
- Fetch strokes from `ObjectCache.getInstance().fetchBasicStroke(w)`, `fetchZoomableStroke(w)` or
|
||||
`fetchStroke(w, dash, phase, cap)`. Do not allocate `new BasicStroke(...)` in `draw()`.
|
||||
- Honour `outlineMode` and every `ComponentState` (`NORMAL`, `SELECTED`, `DRAGGING`) the way the
|
||||
neighbouring components do.
|
||||
- `drawIcon(g2d, width, height)` is a separate, hand-drawn miniature for the toolbox — it is not a
|
||||
scaled call into `draw()`.
|
||||
|
||||
### Connectivity
|
||||
|
||||
If the component conducts, wrap the conductive drawing in
|
||||
`drawingObserver.startTrackingContinuityArea(true)` / `stopTrackingContinuityArea()`, exactly as the
|
||||
comparable component does. Getting this wrong silently corrupts continuity checks and the netlist
|
||||
without any visible rendering difference. `startTracking()` / `stopTracking()` control whether
|
||||
drawing contributes to the component's clickable area.
|
||||
|
||||
Also implement, where relevant:
|
||||
|
||||
- `getControlPointCount()`, `getControlPoint(int)`, `setControlPoint(Point2D, int)`,
|
||||
`isControlPointSticky(int)`, `getControlPointNodeName(int)` — the connection points.
|
||||
- `getInternalLinkName(int, int)` and `getSectionNames(int)` — internal conduction between points,
|
||||
needed by the netlist and analyzers.
|
||||
- `IGerberComponentSimple` or `IGerberComponentCustom` if the part should appear in Gerber output.
|
||||
|
||||
## Rotation and mirroring
|
||||
|
||||
These live in an `IComponentTransformer` under
|
||||
`diylc-library/src/main/java/org/diylc/components/transform/`, referenced from the `transformer`
|
||||
attribute of `@ComponentDescriptor` — never inside the component itself. Reuse
|
||||
`SimpleComponentTransformer` or an existing transformer where the behaviour matches; write a new one
|
||||
only when the part rotates or mirrors differently from everything that exists.
|
||||
|
||||
## Datasheets and variants
|
||||
|
||||
If the part has a catalogue of real-world values and sizes, set `enableDatasheet = true` and
|
||||
`datasheetCreationStepCount`, and add the file under
|
||||
`diylc-library/src/main/resources/datasheets/`. Follow the format of an existing `.datasheet` file
|
||||
and add a spot-check to `DatasheetServiceTest`.
|
||||
|
||||
## Backward compatibility
|
||||
|
||||
Once a component ships, its serialized shape is a contract:
|
||||
|
||||
- Never rename, remove, or retype a serialized field, and never rename or move the class.
|
||||
- To replace a property, keep the old field, mark the old accessors `@Deprecated`, add the new field,
|
||||
and migrate lazily in the new getter — see `AbstractTransparentComponent.getAlpha()`,
|
||||
`Resistor.getPowerNew()`, `AbstractFilmCapacitor.getVoltageNew()`.
|
||||
- Fields added later arrive as `null` from old files; default them in the getter, not the
|
||||
constructor.
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
mvn -q -pl diylc-library -am test
|
||||
```
|
||||
|
||||
Then state plainly that the component still needs a visual check in the running app — automated tests
|
||||
do not cover component appearance. Offer a regression-suite run (see the `regression-test` skill) for
|
||||
any change to an existing component's drawing or serialization.
|
||||
@@ -0,0 +1,129 @@
|
||||
---
|
||||
name: add-plugin
|
||||
description: Add or modify a DIYLC Swing plugin, menu action, toolbar item, dialog or side panel. Use when asked to add a UI feature, a menu entry, a new tool window, a background task, or to react to domain events like selection or project changes.
|
||||
---
|
||||
|
||||
# Adding a plugin or UI feature to DIYLC
|
||||
|
||||
The Swing module is a shell that hosts plugins. Everything the user sees — the toolbox, the canvas,
|
||||
every menu, the status bar, autosave — is an `IPlugIn`. Adding UI means adding or extending a plugin,
|
||||
not reaching into frames.
|
||||
|
||||
## Where things go
|
||||
|
||||
| Path | Contents |
|
||||
| --- | --- |
|
||||
| `diylc-swing/src/main/java/org/diylc/swing/plugins/<name>/` | One package per plugin |
|
||||
| `diylc-swing/src/main/java/org/diylc/swing/actions/` | `AbstractAction` implementations, grouped into `file`, `edit`, `analyze` sub-packages |
|
||||
| `diylc-swing/src/main/java/org/diylc/swing/gui/` | `MainFrame`, dialogs, editors, shared components |
|
||||
| `diylc-swing/src/main/java/org/diylc/swing/gui/MainFrame.java` | Where plugins are installed |
|
||||
|
||||
## Before writing anything
|
||||
|
||||
Read the plugin closest to what is being asked. `plugins/autosave/AutoSavePlugin.java` is the
|
||||
smallest complete example (event subscription plus background work);
|
||||
`plugins/statusbar/`, `plugins/layers/` and `plugins/analyze/` show menu and panel injection.
|
||||
Match the one you read.
|
||||
|
||||
## The plugin
|
||||
|
||||
```java
|
||||
/* ... GPL header from HEADER.txt ... */
|
||||
package org.diylc.swing.plugins.myfeature;
|
||||
|
||||
public class MyFeaturePlugin implements IPlugIn {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(MyFeaturePlugin.class);
|
||||
|
||||
private ISwingUI swingUI;
|
||||
private IPlugInPort plugInPort;
|
||||
|
||||
public MyFeaturePlugin(ISwingUI swingUI) {
|
||||
this.swingUI = swingUI;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connect(IPlugInPort plugInPort) {
|
||||
this.plugInPort = plugInPort;
|
||||
swingUI.injectMenuAction(new MyAction(plugInPort), "Tools");
|
||||
}
|
||||
|
||||
@Override
|
||||
public EnumSet<EventType> getSubscribedEventTypes() {
|
||||
return EnumSet.of(EventType.SELECTION_CHANGED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void processMessage(EventType eventType, Object... params) {
|
||||
if (eventType == EventType.SELECTION_CHANGED) {
|
||||
...
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Install it in `MainFrame` alongside the others:
|
||||
|
||||
```java
|
||||
presenter.installPlugin(() -> new MyFeaturePlugin(this));
|
||||
```
|
||||
|
||||
Order matters for menus and for docked panels — insert the line where the resulting menu or panel
|
||||
position is correct, not blindly at the end.
|
||||
|
||||
## Talking to the domain
|
||||
|
||||
Plugins reach the domain **only** through `IPlugInPort`. Never import a concrete component type into
|
||||
a plugin unless the feature is genuinely component-specific, and never call into `Presenter`
|
||||
directly. If `IPlugInPort` lacks what you need, adding a method there is the right move — but it also
|
||||
means touching `diylc-core`, so keep the addition minimal and in the style of its neighbours.
|
||||
|
||||
## Reacting to events
|
||||
|
||||
`getSubscribedEventTypes()` returns an `EnumSet<EventType>`. Subscribe to the narrowest set that
|
||||
works. `PROJECT_MODIFIED`, `REPAINT` and `MOUSE_MOVED` fire constantly — anything non-trivial in
|
||||
response to those will be felt as lag. The available types are in
|
||||
`diylc-core/src/main/java/org/diylc/common/EventType.java` (`PROJECT_LOADED`, `PROJECT_SAVED`,
|
||||
`SELECTION_CHANGED`, `ZOOM_CHANGED`, `SLOT_CHANGED`, `LAYER_STATE_CHANGED`, `CLOUD_LOGGED_IN`, …).
|
||||
|
||||
## Injecting UI
|
||||
|
||||
- `swingUI.injectMenuAction(action, "Menu Name")` — a menu item; passing a `null` action inserts a
|
||||
separator, and a missing menu is created automatically.
|
||||
- `swingUI.injectSubmenu(name, icon, parentMenuName)` and
|
||||
`injectDynamicSubmenu(name, icon, parentMenuName, handler)` for submenus.
|
||||
- `swingUI.injectGUIComponent(component, SwingConstants.LEFT, collapsible, visibilityConfigKey)` for
|
||||
a docked panel. `TOP`, `BOTTOM`, `LEFT`, `RIGHT` are valid; `CENTER` is reserved for the canvas.
|
||||
Pass a config key for collapsible panels so the open/closed state persists.
|
||||
- `swingUI.getOwnerFrame()` is the parent for dialogs.
|
||||
|
||||
## Actions
|
||||
|
||||
Actions are small classes extending `javax.swing.AbstractAction`, holding an `IPlugInPort`, setting
|
||||
their label with `putValue(AbstractAction.NAME, ...)`, and delegating to `plugInPort` in
|
||||
`actionPerformed`. See `actions/RenumberAction.java` for the minimal shape. They carry
|
||||
`private static final long serialVersionUID = 1L;` like any Swing class.
|
||||
|
||||
## Background work
|
||||
|
||||
Never block the EDT. Use `swingUI.executeBackgroundTask(task, blockUI)` with an `ITask<T>`, which
|
||||
handles the busy cursor and optional glass pane, or a dedicated executor for periodic work as
|
||||
`AutoSavePlugin` does.
|
||||
|
||||
## Settings and strings
|
||||
|
||||
- Persisted preferences: `ConfigurationManager.getInstance().readString(KEY, default)` and
|
||||
`writeValue(KEY, value)`, with the key as a `String` constant. Implement `IConfigListener` to react
|
||||
to changes.
|
||||
- Every user-visible string goes through `LangUtil.translate("...")`.
|
||||
- Icons come from `IconLoader`.
|
||||
|
||||
## Verify
|
||||
|
||||
```bash
|
||||
mvn -q -pl diylc-swing -am compile
|
||||
```
|
||||
|
||||
Compilation is not evidence that the UI works. Say so, and offer to walk through launching the app
|
||||
via `.run/DIYLCStarter.run.xml` (which carries the required `--add-opens` / `--add-exports` flags) for
|
||||
a visual check.
|
||||
@@ -0,0 +1,84 @@
|
||||
---
|
||||
name: regression-test
|
||||
description: Run the DIYLC end-to-end regression suite, which renders ~700 sample .diy projects and compares PNG exports and netlists against reference outputs. Use after any change to rendering, component drawing, serialization, netlist or analyzer logic, or when asked to check for regressions or regenerate reference files.
|
||||
---
|
||||
|
||||
# Running the DIYLC regression suite
|
||||
|
||||
`RegressionTestRunner` (in `diylc-swing/src/test/java/org/diylc/`) loads every sample project under
|
||||
`diylc-regression-data/input`, exports a PNG and extracts netlists, and compares both against
|
||||
reference files produced by a known-good build. It is the only automated coverage of component
|
||||
appearance and of the file format end to end — unit tests do not touch either.
|
||||
|
||||
**Always ask before running the full suite**, and never run `PREPARE` without explicit instruction.
|
||||
|
||||
## Modes
|
||||
|
||||
- **`TEST`** — compares against the reference files and writes a CSV report plus visual diff images.
|
||||
This is what you almost always want.
|
||||
- **`PREPARE`** — *regenerates* the reference PNGs and netlists from the current build. This
|
||||
overwrites the golden copy and is correct only once a human has inspected the differences and
|
||||
decided they are intended. Never run it on your own initiative.
|
||||
|
||||
## Easiest path
|
||||
|
||||
If the maintainer is at their IDE, the `.run/RegressionTestRunner.run.xml` IntelliJ configuration
|
||||
already has the arguments and JVM flags. Offer that first.
|
||||
|
||||
## From the terminal
|
||||
|
||||
The runner needs the `--add-opens` / `--add-exports` flags and a classpath spanning all three
|
||||
modules; `mvn exec:java` will not work because it runs in Maven's own classloader and fails to find
|
||||
Reflections. Build a classpath and fork a JVM:
|
||||
|
||||
```bash
|
||||
cd diylc
|
||||
SP=/tmp # or the session scratchpad
|
||||
|
||||
mvn -q -pl diylc-swing -am test-compile
|
||||
mvn -q -pl diylc-swing dependency:build-classpath -Dmdep.outputFile=$SP/cp-swing.txt -Dmdep.includeScope=test
|
||||
mvn -q -pl diylc-core dependency:build-classpath -Dmdep.outputFile=$SP/cp-core.txt -Dmdep.includeScope=test
|
||||
mvn -q -pl diylc-library dependency:build-classpath -Dmdep.outputFile=$SP/cp-lib.txt -Dmdep.includeScope=test
|
||||
|
||||
java \
|
||||
--add-exports java.desktop/com.apple.eawt.event=ALL-UNNAMED \
|
||||
--add-exports java.desktop/com.apple.eio=ALL-UNNAMED \
|
||||
--add-opens java.base/java.util=ALL-UNNAMED \
|
||||
--add-opens java.base/java.lang=ALL-UNNAMED \
|
||||
--add-opens java.base/java.text=ALL-UNNAMED \
|
||||
--add-opens java.desktop/java.awt=ALL-UNNAMED \
|
||||
--add-opens java.desktop/java.awt.font=ALL-UNNAMED \
|
||||
--add-opens java.desktop/java.awt.geom=ALL-UNNAMED \
|
||||
-cp "diylc-swing/target/test-classes:diylc-swing/target/classes:diylc-core/target/classes:diylc-library/target/classes:$(cat $SP/cp-swing.txt):$(cat $SP/cp-core.txt):$(cat $SP/cp-lib.txt)" \
|
||||
org.diylc.RegressionTestRunner \
|
||||
"$(cd .. && pwd)/diylc-regression-data" TEST [filter]
|
||||
```
|
||||
|
||||
Sanity-check the log line `Presenter - Current DIYLC version: 6.4.0` near the end. If it reports
|
||||
`5.0.0`, the `--add-opens` flags are missing and the run is invalid.
|
||||
|
||||
### Arguments
|
||||
|
||||
1. Path to `diylc-regression-data`
|
||||
2. `TEST` or `PREPARE`
|
||||
3. Optional filter — a substring matched against file paths. **Use it.** The full suite is ~700
|
||||
projects; while narrowing down an issue, run one file or one directory at a time
|
||||
(e.g. `splitter2`, or `guitar`).
|
||||
|
||||
## Reading the results
|
||||
|
||||
- A CSV report is written to `diylc-regression-data/reports/`, named with the timestamp and version,
|
||||
listing each file as OK or failed with the reason (`Images do not match`, `Netlists do not match
|
||||
with switches`, `Netlists do not match without switches`).
|
||||
- Rendered output and **visual diff images**, with red rectangles around every differing region, go
|
||||
to `diylc-regression-data/output/` (not under version control).
|
||||
- Reports are kept as historical evidence. If a run was exploratory rather than a real verification,
|
||||
delete the generated CSV afterwards so the reports directory stays meaningful — and check
|
||||
`git status` before finishing, since the runner writes into the repo.
|
||||
|
||||
## Interpreting a failure
|
||||
|
||||
A difference is not automatically a bug, but it is always a change the maintainer must see. Report
|
||||
which files differ and what kind of difference it is; where an image diff exists, point at the file
|
||||
so it can be looked at. Do not conclude "no regressions" from a filtered run — say exactly which
|
||||
subset was run.
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
CLAUDE.md
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
# DIY Layout Creator — agent guidelines
|
||||
|
||||
DIYLC is a Java 21 Swing desktop application for drawing circuit layouts, schematics and guitar
|
||||
wiring diagrams. It is a mature codebase (~690 source files) with long-standing conventions and a
|
||||
file format that must stay readable by every version. **Consistency with the surrounding code
|
||||
outweighs personal preference or modern idiom in every case.**
|
||||
|
||||
## The one rule that matters most
|
||||
|
||||
Before writing anything, read two or three existing files that do the same kind of job — the
|
||||
neighbouring component, the sibling plugin, the analyzer next to the one you are touching — and
|
||||
match them. Match their naming, their structure, their comment density, their ordering of members,
|
||||
their choice of helper classes. If your change looks different from its neighbours, it is wrong even
|
||||
if it compiles and works.
|
||||
|
||||
## Modules
|
||||
|
||||
Maven reactor rooted at `diylc/pom.xml`, version `6.4.0`, three modules with a strict one-way
|
||||
dependency chain:
|
||||
|
||||
| Module | Contains | Depends on |
|
||||
| --- | --- | --- |
|
||||
| `diylc-core` | Domain model, `Presenter`, rendering pipeline, messaging, serialization, netlist engine, annotations | — |
|
||||
| `diylc-library` | Concrete component classes, transformers, datasheets, legacy file parsers, component-aware analyzers | `diylc-core` |
|
||||
| `diylc-swing` | Swing shell, plugins, dialogs, editors, `DIYLCStarter` | `diylc-core`, `diylc-library` |
|
||||
|
||||
**`diylc-core` must never reference a concrete component type.** It knows components only through
|
||||
`IDIYComponent` and the annotations. If core logic seems to need knowledge of a specific component,
|
||||
that knowledge belongs in `diylc-library` (an analyzer, a transformer, a parser) or is exposed
|
||||
through a new interface in core that the component implements — look at
|
||||
`org.diylc.core.gerber.IGerberComponentSimple` for the established shape of that escape hatch.
|
||||
|
||||
Component and plugin classes are discovered at runtime by Reflections scanning the `org.diylc`
|
||||
package (`ComponentProcessor`). There is no registry to update when adding a component.
|
||||
|
||||
## Build and verify
|
||||
|
||||
Run from `diylc/`. Before reporting a change as done:
|
||||
|
||||
```bash
|
||||
mvn -q -pl diylc-core -am compile # after core changes
|
||||
mvn -q -pl diylc-library -am test # after library changes (~20s)
|
||||
mvn -q -pl diylc-swing -am compile # after swing changes
|
||||
mvn -q test # whole reactor, when the change spans modules
|
||||
```
|
||||
|
||||
Compile the module you touched plus everything downstream of it, and run the unit tests of any
|
||||
module whose tests could plausibly cover the change. Do not report success without having actually
|
||||
run these — quote the failure if something breaks rather than describing it.
|
||||
|
||||
Do not run `mvn package`, `build-all-profiles.sh`, or the regression suite unless asked; they are
|
||||
slow and the deployment build needs Docker and signing certificates. The regression suite is
|
||||
described in `.claude/skills/regression-test/SKILL.md` and is worth *offering* whenever a change
|
||||
affects rendering, serialization or netlist output.
|
||||
|
||||
The app runs from the `.run/DIYLCStarter.run.xml` IntelliJ configuration, which carries the required
|
||||
`--add-opens`/`--add-exports` JVM flags. Copy them if launching from a terminal.
|
||||
|
||||
## Code style
|
||||
|
||||
The formatter is `eclipse-java-google-style.xml` (Google Java Style):
|
||||
|
||||
- **Two-space indent**, four spaces for continuation lines. No tabs.
|
||||
- **100-column** line limit.
|
||||
- Source encoding is **ISO-8859-1** — do not introduce non-ASCII characters into `.java` files. Use
|
||||
Unicode escapes or the resource bundles instead.
|
||||
- Braces on the same line; single-statement `if` bodies without braces are common in this codebase
|
||||
and acceptable when you are matching the surrounding style.
|
||||
- Imports: `java.*` first, then third-party, then `org.diylc.*`, separated by blank lines. No
|
||||
wildcard imports.
|
||||
- Constants are `UPPER_SNAKE_CASE`. Note that component appearance constants are deliberately
|
||||
`public static` and **not** `final` (`BODY_COLOR`, `DEFAULT_WIDTH`), so they can be overridden at
|
||||
runtime; keep that convention when adding new ones.
|
||||
- `private static final Logger LOG = Logger.getLogger(TheClass.class);` — log4j 1.2, declared first
|
||||
among the static members.
|
||||
|
||||
### Comments
|
||||
|
||||
Write comments only where the code cannot speak for itself: a non-obvious electrical or geometric
|
||||
reason, a workaround for a library bug, a compatibility constraint, a unit or coordinate-system
|
||||
assumption. Never restate the code.
|
||||
|
||||
```java
|
||||
// BAD — adds nothing
|
||||
// set the body color
|
||||
g2d.setColor(bodyColor);
|
||||
|
||||
// GOOD — explains something the reader cannot infer
|
||||
// the outer foil marking is drawn on the lead that is closer to the first control point,
|
||||
// which is the convention printed on most film capacitors
|
||||
```
|
||||
|
||||
Javadoc on public interfaces and abstract base classes in `diylc-core` is expected and should carry
|
||||
`@author`. Concrete component classes generally carry no Javadoc — do not add it just to fill space.
|
||||
|
||||
### License header
|
||||
|
||||
**Every new source file starts with the GPL header**, copied verbatim from `HEADER.txt` (or from any
|
||||
existing class), immediately followed by the `package` declaration with no blank line between the
|
||||
closing `*/` and `package`. This applies to Java files in all three modules, including tests.
|
||||
|
||||
## File-format backward compatibility
|
||||
|
||||
DIYLC projects are XStream-serialized object graphs. Users open decade-old `.diy` files with the
|
||||
current build, so **the serialized shape of a component class is a public contract**.
|
||||
|
||||
- Never rename or delete a serialized field, and never change its type. To replace one, keep the old
|
||||
field, add a new one, mark the old accessors `@Deprecated`, and migrate lazily inside the new
|
||||
getter. The canonical examples are `AbstractTransparentComponent.getAlpha()` (legacy `Byte alpha`
|
||||
→ `Percentage alphaPercent`) and `Resistor.getPowerNew()` / `AbstractFilmCapacitor.getVoltageNew()`.
|
||||
- Fields added to an existing class deserialize as `null` in old files. Every read path must tolerate
|
||||
that, typically by defaulting in the getter rather than the constructor.
|
||||
- Never rename or move a component class that has shipped. If a component genuinely must change
|
||||
identity, an alias has to be registered on the XStream side and the legacy parsers in
|
||||
`V1FileParser` / `V2FileParser` reviewed.
|
||||
- Prefer **a `Version` property on a single component class over a new class per revision** when a
|
||||
board or part gains a new variant. One class with a version enum keeps old files loading and keeps
|
||||
the toolbox uncluttered.
|
||||
- Any change here calls for a regression-suite run.
|
||||
|
||||
## Components
|
||||
|
||||
See `.claude/skills/add-component/SKILL.md` for the full recipe. The rules that apply to *editing*
|
||||
existing components too:
|
||||
|
||||
- Extend the most specific abstract base that fits (`AbstractLeadedComponent`,
|
||||
`AbstractRadialComponent`, `AbstractBoard`, `AbstractTransparentComponent`, …) rather than
|
||||
reimplementing shared behaviour.
|
||||
- Every editable property needs a matching getter/setter pair with `@EditableProperty` on the
|
||||
**getter**; the property name is derived from the getter unless `name` is given.
|
||||
- Drawing code must fetch strokes from `ObjectCache.getInstance().fetchBasicStroke(...)` /
|
||||
`fetchZoomableStroke(...)` / `fetchStroke(...)` rather than allocating `new BasicStroke(...)`.
|
||||
`draw()` runs on every repaint; do not allocate what can be cached or reused.
|
||||
- Conductive areas are reported to the `IDrawingObserver` via
|
||||
`startTrackingContinuityArea(true)` / `stopTrackingContinuityArea()`. Getting this wrong silently
|
||||
breaks continuity checks and the netlist, so mirror a neighbouring component exactly.
|
||||
- Rotation and mirroring live in an `IComponentTransformer` referenced from `@ComponentDescriptor`,
|
||||
never inside the component.
|
||||
- `drawIcon()` draws into a small fixed box for the toolbox; it is hand-drawn, not scaled from
|
||||
`draw()`.
|
||||
|
||||
## Plugins and Swing
|
||||
|
||||
- Everything in the UI is an `IPlugIn` installed from `MainFrame` via `presenter.installPlugin(...)`.
|
||||
- Plugins talk to the domain only through `IPlugInPort`, and react to domain changes by returning a
|
||||
narrow `EnumSet<EventType>` from `getSubscribedEventTypes()`. Subscribe to as little as possible —
|
||||
`PROJECT_MODIFIED` fires constantly.
|
||||
- UI is injected through `ISwingUI.injectMenuAction` / `injectGUIComponent` / `injectSubmenu`, not by
|
||||
reaching into frames directly.
|
||||
- Persisted settings go through `ConfigurationManager.getInstance().readString/writeValue` with a
|
||||
`String` key constant.
|
||||
- User-visible strings are wrapped in `LangUtil.translate(...)`.
|
||||
- Long-running work belongs on a background executor, never on the EDT.
|
||||
|
||||
## Testing
|
||||
|
||||
JUnit 4 under `src/test/java` in each module. Add tests where the project already tests: switching
|
||||
logic, netlist and analyzer behaviour, datasheet parsing, core utilities. Component rendering and
|
||||
user interaction are covered by the regression suite instead, not by unit tests. Test classes are
|
||||
named `*Test` or `*Tests` and carry the license header like any other file.
|
||||
|
||||
## Git
|
||||
|
||||
**Do not commit or push.** Make the change in the working tree and stop; the maintainer reviews and
|
||||
commits. If a commit is explicitly requested, commit only what was asked for, never to a remote, and
|
||||
never `git add -A` over unrelated dirty files.
|
||||
|
||||
## Out of scope unless explicitly asked
|
||||
|
||||
- `diylc-server-api/` — PHP backend that runs in an isolated environment on diy-fever.com and cannot
|
||||
be tested locally.
|
||||
- `deploy/`, `build-all-profiles.sh`, packaging and notarization.
|
||||
- `lib/` — vendored jars for dependencies not on Maven Central.
|
||||
- `diylc-regression-data/reports/` — historical evidence, append-only.
|
||||
- Bumping the project version in the POMs.
|
||||
|
||||
## Task recipes
|
||||
|
||||
Detailed, step-by-step recipes live in `.claude/skills/` and are worth reading in full before
|
||||
starting the corresponding kind of work:
|
||||
|
||||
| Skill | Covers |
|
||||
| --- | --- |
|
||||
| `add-component` | Creating or substantially changing a component in `diylc-library` |
|
||||
| `add-plugin` | Adding UI: plugins, menu actions, panels, dialogs, background tasks |
|
||||
| `regression-test` | Running the end-to-end regression suite and reading its report |
|
||||
|
||||
## Reference
|
||||
|
||||
Chapter 13 of *Mastering DIY Layout Creator* is the authoritative developer guide and covers the
|
||||
architecture, extension points and regression suite in prose.
|
||||
Reference in New Issue
Block a user