**Title:** Views are not updated when a diagram presentation is removed

**Description:**
When a `Presentation` (diagram item) is removed from a `Diagram` — for example, by unlinking or deleting the element — the diagram’s registered views are never informed about the removal. This causes stale items to remain visible in the UI. The `Diagram` class already provides an `_update_views` method that accepts a `removed_items` parameter, but no code path currently calls it with a removed item.

**Expected behavior:**
As soon as a presentation is removed from the diagram’s `ownedPresentation` association, the diagram should notify all registered views by calling `_update_views(removed_items=(presentation,))`.

**Implementation hints:**
- The `Diagram` object can use its existing event‑watcher machinery (`self.watcher()`) to observe the `"ownedPresentation"` path.
- When an `AssociationDeleted` event fires for that path, the handler should extract the `event.old_value` and pass it to `_update_views` as a removed item. Be defensive: only act when `old_value` is not `None`.
- The watcher subscription must be cleaned up when the diagram itself is unlinked (`Diagram.unlink()`), otherwise dangling handlers will leak and may fire after the diagram is gone.
- The fix should work regardless of how the presentation is removed (direct `unlink()`, deletion via the modelling layer, etc.).

*Note:* Some existing unit tests for `Diagram` may need to ensure an `ElementDispatcher` is properly configured, because the event‑watcher relies on it. Please verify that all related tests still pass after the change.

Trigger view updates when presentations are removed


```
Title: Extract ours/theirs blob contents for a git-conflicted file
Priority: medium
Type: feature

We need a helper in `gaphor.storage.mergeconflict` that, given a file path,
returns the raw bytes of the "ours" and "theirs" versions when the file is in
a Git merge conflict. This will be used by higher-level model merging code to
resolve conflicted model files.

The function should:

- Locate the Git repository containing the file (using pygit2.discover_repository).
- If no repository is found, or the repository has no conflicts, return
  `(None, None)` without raising an exception.
- Walk the repository's index conflicts. If the file matches an entry,
  retrieve and return the blob data for the ours and theirs sides of the
  conflict.
- Return `(None, None)` if the file is not part of any recorded conflict.

Edge cases:
- The file may be given as an absolute path; the repository's workdir and the
  conflict’s relative path must be joined correctly for comparison.
- The function must not crash if pygit2 is not installed or if the repository
  is in an unexpected state – use standard return values to signal absence.

The implementation should live in the existing `gaphor/storage/mergeconflict.py`
module, alongside the text-based `split` helper. No new dependencies beyond
pygit2 are expected; pygit2 is already a dependency of the storage tests.
```

Add simple resolver for Git-based merge conflicts


## Problem

The diagram export functions (`save_svg`, `save_png`, `save_pdf`, and their helpers `render`, `new_painter`) live exclusively inside the `gaphor.plugins.diagramexport` plugin. Exporting diagrams is a core capability of the application, not a plugin‑specific feature. Other parts of the codebase (for example, the Sphinx extension `gaphor/extensions/sphinx.py`) need these functions, but the only way to get them today is to import from the plugin module. This creates an unnecessary dependency on the plugin system for a fundamental operation and makes the export API harder to discover.

## Expected Behavior

- The export functions should be available from a non‑plugin location that reflects their role as core diagram functionality – a natural home is `gaphor.diagram.export`.
- The existing diagramexport plugin must continue to expose the same public names (`save_svg`, `save_png`, `save_pdf`) so that any external code already importing from `gaphor.plugins.diagramexport` is **not** broken.
- Any internal consumer that currently imports from the plugin (such as the Sphinx extension) should be switched to the new location.

## Impact

Without this change, the codebase couples core functionality to a specific plugin, making the architecture less clear and possibly causing circular‑dependency or maintainability headaches later. Moving the functions and keeping the plugin as a thin re‑export layer resolves the issue without breaking existing users.

Move image save functions to gaphor.diagram.export


**Behavior-first implementation request**

---

**Title:** Undo of an element deletion from many-association does not preserve original position

**Observable problem**
When a user removes an element from a to-many association (a property whose multiplicity is many, e.g., a list of items), and then invokes Undo, the restored element is always appended at the end of the list, regardless of where it was before the deletion.

This can be reproduced with any ordered many-association:

1. Programmatically or through the UI, create three elements and assign them to a many-association in a specific order, resulting in a collection `[A, B, C]`.
2. Delete element `B` (the middle one).
3. Perform an undo operation.
4. Inspect the association collection.

**Expected behavior**
The collection should again be `[A, B, C]` — identical to the state before the deletion. The undo should restore the element’s original index.

**Actual behavior**
The collection becomes `[A, C, B]` (the deleted element is re-added at the end). The ordering is lost.

**Context and constraints**
- For single-valued associations (upper = 1), undo already works correctly because ordering is not a concern; this bug applies only to many-valued associations where order is intentionally preserved.
- If the element appears more than once, each occurrence should be restored to its specific index. However, note that the modeling system typically enforces uniqueness; even so, if a re-inserted element had multiple positions, undo should target the exact position that was removed. If the index information is not available for some edge-case event type (e.g., derived deletions), falling back to appending is acceptable — but the normal undo of a direct association deletion must always restore position.
- The fix must work when undo is performed both through the UI undo manager and programmatically via the UndoManager service.
- This behavioral requirement also applies to redefined properties (properties that shadow an association); undoing a deletion there must also restore the correct index.

**Goal**
Ensure that undoing a deletion from an ordered many-association puts the element back at the same index it occupied before the deletion, so that the collection after undo is identical to the collection before delete.


Ensure that diagram lines (connections) are always rendered on top of shapes and other elements. Currently lines can draw behind items, making them hard to see or select. The fix should guarantee:

- All line items appear after all non-line items in the draw order, so they are visually on top.
- This must hold even when a line has a parent (is grouped inside another item); it still draws after everything.
- The relative order among non-line items, and among line items themselves, should remain the same — only the separation of lines to the top is needed.

Always render diagrams with lines on top


**Title:** Association end property visibility should be respected when only multiplicity is provided

**Description:**
When editing an association end property by typing a UML notation string (e.g. `-0..2` or `-[0..2]`), the leading visibility symbol (`-`, `+`, `#`, `~`) is silently ignored if only a multiplicity is entered. The multiplicity is parsed correctly, but the visibility remains unchanged. This makes it impossible to set both visibility and multiplicity without also providing a name or type.

**Acceptance Criteria:**
- Entering `-0..2` or `-[0..2]` for an association end must:
  - Set the property’s visibility to `private`.
  - Set the lower bound to `"0"` and the upper bound to `"2"`.
  - Leave the property’s name, type, and default value empty/unchanged.
- The same behaviour must hold for the other UML visibility symbols (`+` – public, `#` – protected, `~` – package) when combined with a multiplicity and no name.
- Existing parsing of full notation (e.g. `+name: Type [0..1]` or `-name`) must not be affected.

Allow to set visibility if only multiplicity is provided


**Title**: Association ends are incorrectly groupable into classifiers, corrupting the model

**Description**
When a `UML.Property` is an association end (a member end of a `UML.Association`), it should never be moved into a classifier as an owned attribute. Currently, the grouping functionality (e.g., `group()` from `gaphor.diagram.group`) allows such an association end to be grouped into a classifier like `Class`, `Interface`, `Artifact`, or `DataType`. This breaks the association structure because the end is silently removed from the association and becomes a plain owned attribute of the classifier.

**Steps to reproduce**
1. Create two classes and an association between them, so that at least one end is navigable and kept as an association-owned property.
2. Obtain the `UML.Property` for that association end (e.g., `association.memberEnd[0]`).
3. Call `group(another_classifier, association_end)`, or attempt the equivalent diagram drag-and-drop that triggers grouping.

**Actual behavior**
- `group()` returns `True` and the association end is moved out of the association into the classifier’s `ownedAttribute`. The association is left in an invalid state.
- `ungroup()` on an association end that was somehow mis‑owned (or even directly) also acts on it, potentially deleting it from `ownedAttribute`.

**Expected behavior**
- `group(classifier, property)` must return `False` for any `UML.Property` that is an association end (i.e., `property.association is not None`). No model changes should occur.
- `ungroup(classifier, property)` must not act on an association end either; it should return `False` (or simply not delete the element from `ownedAttribute`) when `property.association is not None`.

This applies to all classifier types that accept `UML.Property` via grouping: `UML.Class`, `UML.Interface`, `UML.Artifact`, and `UML.DataType`. The fix should ensure association ends are never re‑parented through the grouping/ungrouping mechanism.


**Feature request: Distinct visual shapes for all UML pseudostate kinds**

### Context
In state machine diagrams, pseudostate elements (initial, deep history, shallow history, join, fork, junction, choice, entry point, exit point, terminate) currently have no distinguishable visual representation except for the initial pseudostate. All other kinds render as the same empty circle, which violates UML notation and makes diagrams hard to interpret.

### Acceptance criteria
1. **Each pseudostate kind must be drawn with its own UML‑compliant shape:**
   - `initial` – small filled circle (already correct; may be reused for junction)
   - `deepHistory` – circle containing the letter **H** and an asterisk (H*)
   - `shallowHistory` – circle containing the letter **H**
   - `join` / `fork` – thick vertical bar (not a circle); the bar should be narrow horizontally and taller vertically
   - `junction` – small filled black circle (identical to initial)
   - `choice` – a diamond
   - `entryPoint` – an empty circle (no internal marks)
   - `exitPoint` – a circle with an **X** inside
   - `terminate` – a large **X** shape
2. The mapping from pseudostate kind to shape must be **exhaustive**: every value defined in the UML Pseudostate kind enumeration must have an explicit entry; no kind should fall back to a generic placeholder.
3. If the size of the element needs to differ per kind (e.g., join/fork needs a different aspect ratio), the rendered item must reflect that.
4. The rendering should remain consistent with Gaphor’s existing drawing conventions (Cairo stroke/fill, color handling) used by other diagram elements.

### Non‑goals
This request does not cover changing the behavior of the tool palette, state machine connection rules, or the underlying UML data model. It is strictly about the on‑canvas visual presentation of pseudostate items.

Provide presentations for all kinds of pseudostate


**Simplify modelmerge tree model**
Replace the `ChangeSetModel` wrapper in the model merge UI with a direct `Gio.ListStore` of `Node` objects.
- Use the list store as the root for `Gtk.TreeListModel`.
- Populate and refresh it on model load.
- The scrolled window should only be visible when there is at least one change node.
- When a node’s “apply” toggle is changed, also sync all of its descendant nodes so their applicable/applied states update accordingly.
- Convert child lists to list stores inside `Node` (pass children as plain lists, not tuples).


## Bug Report: File dialog cancellation causes GTK error

**Title:** Avoid error when file dialog is cancelled

**Description:**
When using the Gaphor UI to open or save a file (File → Open, File → Save As, etc.), pressing "Cancel" or closing the file dialog triggers a GTK error / traceback instead of simply doing nothing. The error originates from the `open_file_dialog` and `save_file_dialog` functions in the `gaphor.ui.filedialog` module because the dialog result is treated as a successful selection even when the user cancelled.

**Acceptance criteria:**
1. Cancelling the **file open** dialog must leave the application unchanged and must not log or raise any error.
2. Cancelling the **file save / Save As** dialog must behave identically — no error, no file operation.
3. The dialog callback handler must safely ignore a result that represents an error (i.e., cancellation) and exit early without calling `open_multiple_finish` or `save_finish`.


**Title:** Element ID generation is not consistently orderable, making tests non‑deterministic

**Description:**
Model element IDs are created in several places using `uuid.uuid1()` directly.  This makes the assignment order inherently non‑deterministic because the IDs depend on the system clock and random bits that cannot be controlled from outside.

*Consequence:* When sorting elements by their IDs (or relying on any ordering that depends on IDs), different runs of the same model‑creation sequence produce different element orders.  Property‑based tests that need repeatable, deterministic states are therefore unstable and hard to debug because the internal order of elements changes between runs.

**Expected behavior:**
ID generation should be a pluggable, central mechanism so that:

1. The default production path still yields unique identifiers (e.g. time‑based UUIDs).
2. Tests can inject a deterministic generator (for instance a monotonic counter) so that IDs are assigned in a strictly orderable way, corresponding to the order in which elements are created.
3. Every code path that creates an ID must go through this central facility, avoiding ad‑hoc `uuid.uuid1()` calls.

With this in place, sorting a collection of elements by their ID yields a stable, repeatable order that reflects the creation sequence, which in turn makes test assertions and state‑machine rule strategies deterministic.

**Steps to reproduce (observed symptom in tests):**
- Run the model‑consistency hypothesis tests multiple times.
- Observe failures that depend on the order of elements selected from the model (e.g. when picking items from a diagram for copy/paste operations or checking relationships).
- The failures correlate with non‑deterministic ordering of presentations and model elements because the IDs are generated randomly and cannot be used as a predictable tie‑breaker.

**Additional notes:**
- The mechanism should allow a generator to be set once (e.g. at session startup) and then be consumed across the lifetime of the model.
- Care should be taken that any code cloning or upgrading items (like storage upgrades that create new elements) also uses the same central ID source.

Make id generation consistently orderable


When a stereotype attribute (Property) is deleted, any Slot that references it as its definingFeature must be automatically cleaned up from the owning instance specification. This must also work when the attribute is inherited through stereotype generalization — that is, defined on a parent stereotype but used in an applied child stereotype. Do not remove the applied stereotype itself when only the attribute is removed.

Add test to ensure slots are removed if attribute is removed


We should move the `drop()` logic out of the `gaphor.ui` package and into more appropriate diagram- and UML-specific modules. Currently, `drop` lives in `gaphor/ui/drop.py` and mixes generic diagram creation with UML-specific handling for relationships and associations. The generic piece—creating a diagram item for any model element—is a diagram core concern, while UML-specific overrides belong in `gaphor.UML`.

Please implement the following reorganisation without changing the externally observable behavior:

- Extract the generic `drop` (the `singledispatch` default implementation) into a new module `gaphor/diagram/drop.py`.
  The function should accept an `Element`, a `Diagram`, and coordinates `(x, y)`, and create the appropriate diagram item for any element type, returning the item or `None` if no diagram item class is found.

- Move the existing UML-specific handlers out of `gaphor/ui/drop.py` into a new module `gaphor/UML/drop.py`. These should register on the generic `drop` from `gaphor.diagram.drop` as additional dispatch implementations:
  - For `UML.Relationship` subclasses: if both the head and tail elements already have a presentation on the target diagram, create the relationship item and connect its head/tail ends using `postload_connect`. The helper `diagram_has_presentation` should also move to this module.
  - For `UML.Association`: return `None` (currently handled this way; behaviour must stay the same).

- Ensure the UML-specific registrations take effect whenever the `gaphor.UML` package is imported by adding `import gaphor.UML.drop` to `gaphor/UML/__init__.py`. This maintains the existing import-time side effect.

- Update the import in `gaphor/ui/diagrampage.py` to `from gaphor.diagram.drop import drop` and remove the old `gaphor/ui/drop.py` file entirely.

The final result should preserve identical behavior: dropping any model element from the browser onto a diagram creates a diagram item; UML relationships connect their ends if both end elements are present; dropping an Association still does nothing (returns `None`). No other drop behavior should be introduced or altered.

Move drop implementation to a more appropriate place


We need to verify and, if needed, fix the behavior of undo/redo for connected diagram relationships (associations and generalizations). When using the diagram class from `gaphor.core.modeling` (which is the expected concrete diagram type for models), performing undo after creating a relationship item between class items should remove that relationship and its subject model element from the diagram and element factory. Redoing should restore the relationship item with its connections to the class items, the underlying UML model element, and all relevant properties (e.g. member ends for associations) intact. No warnings (log level WARNING or above) should be emitted during these undo/redo cycles.

The following observable invariants should hold after a redo of a transaction that created a connected relationship:
- Exactly one relationship item of the expected type (generalization or association) is present in the diagram.
- Exactly one corresponding UML model element (UML Generalization or UML Association) exists in the element factory.
- For association items: the restored association has two member ends, and head/tail subject references are set.
- The relationship item’s `subject` field points to the restored model element.

Add tests for undoing connected relationships


The way Gaphor dispatches `UnlinkEvent` bypasses the normal event pipeline, leading to stale element state in event handlers and breaking indirect deletion scenarios.

### Current behavior (bug)
- When an element is unlinked, the `ElementFactory` immediately removes it from its internal registry and emits `ElementDeleted` **before** any external handler sees the `UnlinkEvent`. This means a handler subscribed to `UnlinkEvent` (or that would have been triggered by the event) cannot rely on the element still being present or on the correct order of events.
- As a consequence, when one element’s unlink causes another element to be unlinked (an indirect cascade), the order of events is wrong and the internal state may become inconsistent. This is observable as a failing scenario where an indirect deletion does not produce the expected events.
- The `Element._model` attribute is cleared inside `Element.unlink` itself, rather than waiting for the factory to process the removal.

### Desired behavior
1. `UnlinkEvent` must be dispatched through the **event manager** (when available) like any other event, so that all handlers see it before the element is actually removed from the factory.
2. The `ElementFactory` should subscribe to `UnlinkEvent` and perform its internal cleanup (deleting from the registry, setting `_model` to `None`, and emitting `ElementDeleted`) **in response to that event**.
3. When no event manager is present, the factory should still handle `UnlinkEvent` directly as a fallback.
4. When events are **blocked** (inside `block_events`), the default blocking behavior must still allow `UnlinkEvent` to be processed internally; otherwise element unlinking during a blocked phase would leak or leave dangling references.
5. On factory shutdown, any subscription for `UnlinkEvent` should be cleaned up to avoid stale handlers.
6. The `ElementDeleted` event must be emitted only after the internal cleanup has completed, and only if an event manager is available to receive it.

This change should make the indirect-element-deletion scenario work correctly, producing the proper sequence of life-cycle events.

### References
- The event system resides in `gaphor.core.eventmanager`.
- Element unlink flow: `Element.unlink` → `ElementFactory.handle` → event manager.
- The blocking behavior is currently controlled by `BlockingEventManager` used in `ElementFactory.block_events`.


**Title:** StyleSheet fallback properties leak through when a diagram has a custom StyleSheet

**Description:**
When a diagram has an associated StyleSheet (either the default one or a user-supplied one), the styling resolution in `Diagram.style()` still merges a hardcoded fallback dictionary (`FALLBACK_STYLE`) under the properties returned by the stylesheet. This has two undesirable effects:

1. Properties that the stylesheet intentionally leaves unset receive fallback values instead of the system-wide defaults embedded in the stylesheet’s compiled rules.
2. The fallback may override visual aspects that the stylesheet author deliberately left at their compiled default (e.g., those coming from `*` selector rules in the system stylesheet).

**Expected behavior:**
- When `self.styleSheet` is not `None`, `Diagram.style(node)` should return exactly the result of `style_sheet.match(node)`, with no additional merging from `FALLBACK_STYLE`.
- When `self.styleSheet` is `None`, `Diagram.style(node)` should return the content of `FALLBACK_STYLE` as a last resort (the fallback is only used in contexts like tests where no stylesheet is present).
- The system stylesheet (`SYSTEM_STYLE_SHEET` in `stylesheet.py`) must itself provide all the minimal default properties that the fallback used to supply, so that stylesheet-only resolution is visually equivalent. In other words, the fallback properties should be a subset of what a compiled empty/default stylesheet produces.

**Impact:**
Without this change, anyone using a custom StyleSheet may find their styling unintentionally mixed with hardcoded fallback values, making it harder to predict the final appearance and forcing workarounds to explicitly reset those properties.


**Request: Support crash recovery for new unsaved sessions opened from a template**

### Background
Gaphor already has a crash-recovery mechanism that works for models opened from a file. When Gaphor restarts after a crash, it shows a recovery dialog where the user can restore unsaved changes from the previous session.

### Problem
If a user creates a new model from a template (e.g., via the greeter dialog) and performs edits, and then Gaphor crashes, the session's changes are lost. The recovery dialog does not list unsaved template-based sessions.

### Expected behaviour
After a crash, *any* open session – whether started from a file or from a template – must be restorable through the standard recovery dialog.

More concretely:
1. When Gaphor starts a new session from a template, the recovery service must start tracking changes immediately, so that edits made in that session are recorded to the recovery log.
2. The recovery log’s preamble must record that the session was opened from a template (not from a regular file). It should store the template path so that on recovery the original template can be re‑loaded and the recorded changes replayed.
3. When Gaphor restarts and scans the recovery logs, it must recognise template‑based sessions and present them in the recovery dialog. The reconstruction should load the template model first, then apply the captured operations.
4. If the user decides *not* to recover, the recovery log should be cleaned up as usual.

### Constraints / edge cases
- A template session has no file path; the recovery logic must not assume a file path is always present.
- The preamble metadata and the restoration path must correctly distinguish a file‑based session from a template‑based session.
- When a template is loaded (both during normal use and during recovery), the ModelReady event should be emitted so that the recovery service can attach itself and begin recording. Currently this event is only emitted when loading a file, which prevents recording for template‑based sessions.
- The change should not break recovery for existing file‑based sessions.

Support recovery for new unsaved sessions


We need to make `collection` objects hashable. The `collection` class implements equality via `__eq__`, but does not define `__hash__`. This makes instances unhashable, which causes `TypeError: unhashable type: 'collection'` whenever a `collection` is used as a dictionary key, stored in a set, or passed to any cache‑decorated function such as `attrname`.

### acceptance criteria
- Calling `attrname(collection_instance, "some_attr")` must succeed without raising `TypeError`.
- The hash of a `collection` must be consistent with its equality: two equal collections must have equal hashes.
- `collection` objects may be used as keys in dicts and members of sets.

Fix TypeError collection is not hashable


**Request: Remove TaskOwner mixin and inline background task logic into ImagePropertyPage**

The `TaskOwner` mixin in `gaphor.asyncio` is only used by the image property page (`ImagePropertyPage`) to manage a single background async task. This extra layer of abstraction adds complexity without benefit. We want to inline the task management directly into the property page and drop the `TaskOwner` class entirely.

**Acceptance Criteria**

1. `ImagePropertyPage` no longer inherits from `TaskOwner`. Instead, it tracks its own background task internally.
2. Clicking the “Select Picture” button starts an async file‑selection dialog. If the button is clicked again while a dialog is already active, the new click is silently ignored — no second dialog appears.
3. Once the file‑selection dialog finishes (either a file is chosen or the dialog is dismissed), subsequent button clicks can start a new dialog as normal.
4. The `TaskOwner` class and its associated utility methods (`create_background_task`, `cancel_background_task`, `gather_background_task`) are removed from `gaphor/asyncio`. Import statements referencing `TaskOwner` must be cleaned up.
5. No other behavior of the image property page (loading an image, resetting to default size, etc.) is changed.

Use local task reference for image property page


# Replace UML-specific types with core modeling types in diagram subsystem

## Problem
The diagram toolbox (`gaphor/diagram/diagramtoolbox.py`) and the Sphinx documentation extension (`gaphor/extensions/sphinx.py`) currently import type annotations and classes directly from UML (`gaphor.UML.uml.Element`, `gaphor.UML.uml.Diagram`, or the top‑level `gaphor.UML`). This couples the generic diagram infrastructure to the UML modeling language and prevents the diagram core from being used with non‑UML modeling languages such as SysML, RAAML, or C4.

## Expected behavior
- In `diagramtoolbox.py`, all type annotations that currently use `Element` (from `gaphor.UML.uml`) should use the core base class `Base` (from `gaphor.core.modeling`).
- The `Diagram` import used by the Sphinx directive should come from `gaphor.core.modeling`, not from `gaphor.UML`.
- The Sphinx directive accesses `qualifiedName` on a `Diagram` instance. The core `Diagram` class does not provide this attribute (it is defined on UML's `Element`). Add a `# type: ignore[attr-defined]` comment or similar annotation to silence the type checker, since this attribute exists at runtime on objects from the actual element factory.

## Actual behavior
- `DiagramType.allowed`, `ElementCreateInfo` fields, and `new_item_factory` are typed with `gaphor.UML.uml.Element`.
- `gaphor.extensions.sphinx` imports `Diagram` from `gaphor.UML`, even though only the core diagram concept is needed.
- No type‑checker suppression is present for the `qualifiedName` access, which would produce a spurious warning after switching to the core `Diagram`.

## Impact
Language‑agnostic diagram code cannot be written inside the `gaphor.diagram` package without pulling in UML symbols. This blocks broader re‑use of the toolbox and diagram factory for other modeling languages. It also violates the intended architectural layering where core/diagram should depend only on core modeling abstractions, not on a specific metamodel.


Make `ActivityParameterNode` droppable from the model browser onto a diagram.

Currently, dragging an `ActivityParameterNode` onto a diagram does nothing.
When a user drops an `ActivityParameterNode` onto a diagram:

- A new `ActivityParameterNodeItem` must appear at the drop coordinates.
- The item must be attached to the activity item whose subject is the node’s owning `Activity`.
- If multiple activity items exist for the same activity, the node must attach to the one closest to the drop point.


```
Title: Proxy Port attached to a SysML Part/Property does not correctly use the property's type

When a ProxyPort is dropped onto a SysML PropertyItem (a part or property compartment item) that has a type (e.g., a Block), the port should be owned by the type (the Block) and appear nested under the property in the diagram hierarchy. Currently the connection either fails silently or creates a port that is incorrectly linked to the property itself instead of the property's type.

Expected behavior:
- The connector responsible for (BlockItem | PropertyItem, ProxyPortItem) should verify that the subject's type is an EncapsulatedClassifier when the subject is a UML::Property. If the property has no type or the type is not an EncapsulatedClassifier, the connection should be disallowed.
- When the connection is allowed, the newly created ProxyPort's `encapsulatedClassifier` must be set to the property's type (the Block), not to the property itself.
- The ProxyPort item should still become a child of the PropertyItem in the diagram’s visual hierarchy (via `change_parent`).

Current behavior:
- `BlockProperyProxyPortConnector` treats every subject as an EncapsulatedClassifier, so it either rejects a valid PropertyItem whose type is a Block or, if it succeeds, assigns the proxy port to the property instead of the block. This results in an invalid model state.

This prevents users from adding proxy ports to typed properties in SysML block diagrams.
```


# Bug Report: Package name is not positioned correctly when it contains elements, and related grouping issues

## Problem description

### Package name alignment
Currently, the name of a UML package is always displayed centered vertically inside the package shape. This is problematic because:

- A non-empty package looks exactly like an empty package; there is no visual indication that the package has contents.
- In UML notation, it is expected that the package name appears at the top of the box (like a tab label) when the package contains other elements, and centered when it is empty. This standard visual convention helps users quickly distinguish populated packages from empty ones.
- When an element is dragged into a package on a diagram, the package name stays centered and does **not** automatically move to the top position. Refreshing the diagram (e.g. by interacting with the package) may sometimes fix it, but the update should be immediate and reactive.
- After reloading a saved model, the package name may also not respect the correct alignment based on whether the package has visible children in the diagram.

### Duplicate deployments when grouping artifacts on nodes
When an artifact is dropped onto a node multiple times (or moved around), each drop operation creates a brand new `Deployment` relationship, even if one already exists between the same node and artifact. This pollutes the model with duplicate entries and can lead to confusion.

## Desired behavior

1. **Package name placement**:
   - If a package on a diagram **has child elements** (other diagram items visually contained inside it), the package name should be displayed at the **top** of the box, visually mimicking the standard UML tab-style header.
   - If the package **has no child elements** in that diagram, the name should remain **centered** (as it is today).
   - This behavior must be updated **immediately** when children are added to or removed from the package via drag-and-drop, copy/paste, or programmatic changes. Saving and reloading the model must preserve the correct alignment.

2. **Idempotent artifact-to-node grouping**:
   - Dropping an artifact onto a node should **only create a new `Deployment`** if no matching deployment already exists between that specific node and artifact. If a deployment is already present, the operation should be a no-op regarding model changes (though the diagram containment should still be updated).

## Additional context
- The same `PackageItem` is used for both UML packages and profiles, so the alignment logic should apply to both.
- The Node-Artifact grouping issue is related to the drop interaction and may be part of the same code paths that handle diagrammatic containment updates.

These improvements would make the tool more compliant with UML visual conventions and prevent subtle model corruption during interactive modeling.

Place Package name at top if it contains elements


```
Fix AutoLayout: only layout top-level diagram items

The auto-layout plugin currently tries to lay out every presentation in a diagram, including items that are nested inside container elements (for example, classes placed inside a package). Nested items are positioned relative to their container and should not be independently repositioned by the layout engine. This can lead to incorrect or unexpected results when auto-layout is applied to diagrams that contain such nesting.

We need to adjust the auto-layout logic to skip any presentation that has a parent, so that only the root-level items are passed to the pydot layout engine.
```


We need to make the `Application` lifecycle more like `Session` by emitting lifecycle events and removing the GTK-specific `on_quit` callback. Currently the UI startup passes a quit closure into `Application`, and the UI layer manually opens the main window after creating a session. That coupling prevents clean reuse and testability.

**Expected behavior/Invariants:**

- `Application` should emit a `SessionCreated` event (containing the application and the new session) from `new_session()` after the session is created and set as active.
- `Application` should emit an `ApplicationShutdown` event from `shutdown()` before any services are torn down.
- `Application.__init__` no longer accepts an `on_quit` callback. The shutdown code path must not call such a callback.
- In the UI entry point (`gaphor.ui.run`): subscribe to `SessionCreated` to open the main window (instead of calling a helper that does both create and open) and subscribe to `ApplicationShutdown` to call `gtk_app.quit()`. Remove the old `new_session` helper that coupled session creation with window opening.
- `AppFileManager` must not depend on `Gio.Application.get_default()`. Instead the application instance should be provided to `AppFileManager` (e.g., via its constructor). Within `AppFileManager`, creating a new session should call `application.new_session()` directly, without going through `Gtk.Application.open`. Similarly, loading a file should create a new session if the current one is not empty, again using `application.new_session()`.
- The `Application` instance must register an `EventManager` service (by name) so that the UI subscriptions work (`event_manager` service entry added to `gaphor.appservices` plugin group).

No other behavioral changes; the screen should not flicker, and session creation/shutdown order must remain unchanged except for the new event emissions.

Emit application and session create events


**Feature Request: Enable copying an entire model (element factory) via the existing copy/paste pipeline**

The [`copy`](https://github.com/gaphor/gaphor/blob/f8e2c3c/gaphor/diagram/copypaste.py#L44) function already supports single elements and arbitrary iterables of elements, and [`paste_full`](https://github.com/gaphor/gaphor/blob/f8e2c3c/gaphor/diagram/copypaste.py#L159) reconstructs the copied items in a target diagram. However, there is no way to copy an entire `ElementFactory`—the container that holds all model elements, including diagrams and their presentations—as a single unit.

We want `copy(factory)` to work when `factory` is an `ElementFactory` instance, and `paste_full(buffer, target_diagram, lookup)` to faithfully recreate the full model in another factory while leaving the source untouched.

**Acceptance Criteria / Observable Behaviour**

1. **`ElementFactory` can be passed to `copy()`**
   The factory must behave as an iterable of its contained elements so that the existing generic copy dispatcher (which already handles `Iterable[Element]`) can process it without modification.

2. **Whole-model round-trip**
   Given an `ElementFactory` containing an arbitrary mix of elements, diagrams, and presentations (e.g., two classes connected by a generalization, drawn on a diagram), copying the factory and pasting into a new factory that already contains a single empty diagram must:
   - Create exactly one additional diagram in the target factory (the pasted copy of the original diagram).
   - Preserve all non-presentation model elements (classes, packages, relationships, etc.) with correct associations.
   - Reproduce the original diagram’s presentation items (class boxes, generalization lines) on the new diagram with correct connections and visual layout.
   - After paste, the target factory’s total element count equals the source factory’s element count plus one (the pre-existing diagram in the target).

3. **No cycles or duplicated ownership**
   The copy operation must **not** attempt to serialize relations that would create circular references or duplicate ownership in the target model:
   - `Element.presentation` (back-link from model elements to their presentations) must **not** be included in the copied data, because presentations are recreated by the paste step and linked anew.
   - `Diagram.ownedPresentation` must **not** be copied directly when copying a diagram; the owned presentations are handled separately by the presentation copy/paste logic.
   - `Presentation.diagram`, `parent`, and `children` must **not** be part of the copied presentation data. These relationships are rebuilt by `paste_full` when the items are placed on the target diagram.

4. **No regressions to existing copy/paste**
   The behaviour for copying individual diagrams, elements, and presentations (via the UI-based copy/paste) must remain unchanged. The new factory-level copy should reuse the existing copy helpers, not duplicate or bypass them.

**Non-obvious safety constraints**
- The existing `copy_element` already blacklists the `presentation` attribute to avoid cycles. When copying a diagram, an additional blacklisting of `ownedPresentation` is required so that the diagram’s owned presentations are not serialised as part of the diagram’s own data.
- `copy_presentation` must continue to exclude the `diagram`, `parent`, and `children` attributes; its implementation should align with the blacklist mechanism used by `copy_element` to avoid divergence.
- The paste step (`paste_full`) already knows how to create a new diagram if a `Diagram` id appears in the copy buffer and how to link presentations to the target diagram. No changes are needed there as long as the copy buffer is correctly prepared.


# Feature Request: Support CSS Adjacent-Sibling Combinator (`+`) in Style Selectors

## Background

Our diagram styling engine supports a CSS-like selector syntax. Currently, the following combinators are supported:

- **Descendant** (` `) — matches any descendant
- **Child** (`>`) — matches a direct child
- **Parent** (`<`) — matches when the right-hand element is a parent of the left-hand match

Notably absent is the standard CSS **adjacent-sibling combinator** (`+`).

## Request

Add support for the `+` combinator in style selectors so that rules like `foo + bar { ... }` work correctly.

### Observable behavior

When a stylesheet contains a rule with the `+` combinator, e.g.:

```css
previous + sibling {
  /* declarations */
}
```

The selector should match an element named `sibling` **if and only if**:

1. The element has a parent (i.e., it is not a root-level element).
2. The element's immediately preceding sibling within that parent matches the left-hand selector (`previous` in this example).

It should **not** match when:

- The element has no parent.
- The element is the first child of its parent (no preceding sibling exists).
- The preceding sibling exists but does not satisfy the left-hand selector.
- The element itself does not satisfy the right-hand selector.

This follows standard CSS adjacent-sibling semantics: the combinator is non-symmetric and only looks at the single immediately preceding sibling (not all prior siblings).

## Context

This combinator is needed so that we can write style rules targeting elements based on their sibling order within a parent — for example, styling the first element after a region separator in UML/SysML diagrams.

The existing `CombinedSelector` AST node already carries a `combinator` string field that is used to distinguish `" "`, `">"`, and `"<"` in the compiler. The parser side likely already tokenizes `+` without error (since it's standard CSS tokenization), but the compiler raises a `SelectorError("Unknown combinator", ...)` when it encounters a combinator value it doesn't recognize. The primary gap is in the compiler's evaluation logic.

## Acceptance criteria

1. A `CombinedSelector` with `combinator == "+"` compiles to a predicate function that evaluates as described above.
2. The existing combinators (descendant, child, parent) continue to work unchanged.
3. Edge cases: no parent, first-child (index 0), element not found in parent's children collection — all handled gracefully (returning a falsy result, not raising).


When a containment relationship (line between a package and a contained element like a Class, Package, or Diagram) is disconnected, the contained element should not disappear from the model. Instead, reassign the contained element’s package to the diagram’s owning package, so it remains in the project tree under the diagram’s package.

Disconnect will place element in same package as diagram


Issue: Diagram export fails on Windows when a diagram name contains characters that are invalid in filenames.

When exporting a diagram to a file — either via the GUI save dialog or via the `gaphorconvert` command-line tool — the filename is derived from the diagram’s name. Diagram names that include characters not allowed in Windows file names (e.g. `<>:"/\|?*` and other non-word characters) cause the export to fail or produce illegal filenames.

Desired behavior:

- Sanitize the diagram name before using it as part of the filename.
- Replace any character that is not a letter, digit, or underscore (`_`) with an underscore.
- Collapse sequences of such invalid characters into a single underscore (so `"foo<>bar"` becomes `"foo_bar"`, not `"foo__bar"`).
- Preserve Unicode letters (accented, Japanese, etc.) as-is — they must not be replaced.
- If the sanitized name is empty (or becomes empty), fall back to the existing default name `"export"`.
- Apply the sanitization consistently to both the GUI export flow and the `gaphorconvert` script.

Add simple filename escaping for diagram names


## Implementation Request

**Title**: Fix Transaction Handling in SysML Property Pages

**Context**
The Gaphor property page framework uses an explicit `Transaction` context manager for any model modifications. Several base property pages (e.g., `NamePropertyPage`, `NotePropertyPage`) already accept an `event_manager` during construction and wrap model changes in `with Transaction(self.event_manager):`. The SysML property pages, however, still rely on the `@transactional` decorator from `gaphor.core`. This decorator no longer has access to a valid event manager, causing the property pages to fail or silently not commit changes.

**Required Behavior**
All SysML property pages should follow the same pattern as the base property pages:

- Accept an `event_manager` parameter in `__init__`, store it, and use `with Transaction(self.event_manager):` for any operation that modifies model data (setting attributes, creating/unlinking elements, changing properties, etc.).
- Remove all usage of the `@transactional` decorator and the corresponding import from `gaphor.core`.
- Import `Transaction` from `gaphor.transaction` instead.

**Affected Pages**
The classes that need updating include (but may not be limited to):

- `RequirementPropertyPage`
- `RequirementItemPropertyPage`
- `CompartmentPage`
- `InterfaceBlockPage`
- `PropertyAggregationPropertyPage`
- `ItemFlowPropertyPage`

Each of these currently uses `@transactional` on one or more callback methods; the decorator must be removed and the method body wrapped in `with Transaction(self.event_manager):`. The `__init__` signature must be extended to accept `event_manager`.

**Non‑Obvious Edge Cases / Safety Constraints**

- The `Transaction` context manager should wrap only the model‑modifying parts of each callback, not the entire method (e.g., UI helper calls like `set_sensitive` or `set_text` should remain outside the transaction).
- Make sure the `event_manager` is **not** used for anything else; it is solely for the `Transaction` context.
- The fix must not change the public API of these classes, except for the addition of the mandatory `event_manager` constructor parameter (callers are expected to provide it, just as they do for the already‑fixed base property pages).

**Acceptance Criteria**
- All SysML property pages successfully apply model changes when invoked from the UI.
- No `@transactional` decorator references remain in the SysML property pages file.
- The import of `Transaction` from `gaphor.transaction` is used consistently.
- The code passes the existing property page tests (with the necessary test signature adjustments to provide an `event_manager` fixture).

---

**Scores**:
- `supervision_value`: **4**
- `difficulty`: **3**

Fix transaction use in SysML property pages


```markdown
## Feature request: Allow attaching a ProxyPort to a Property shape on a diagram

Currently a ProxyPort can be visually connected to a Block, but not to a Property (SysML). We need to extend the attachment logic so that a ProxyPort can be placed on the boundary of a `PropertyItem` in addition to a `BlockItem`.

**Expected behavior**

- When a user drags a ProxyPort onto a Property shape, the connection should be accepted (the port should snap to the edge of the property, and the diagram linkage should be established).
- The underlying `encapsulatedClassifier` assignment (`proxy_port.subject.encapsulatedClassifier = ...`) should **only** be performed when the owning element’s domain object is an instance of `EncapsulatedClassifier` (as is the case for `Block`). For a `Property` subject that does not satisfy this condition, the assignment must be skipped — otherwise an attribute error may occur or an invalid model state may be created.
- The existing Block ↔ ProxyPort attachment behavior must remain unchanged.

**Non-goals**

- No model-level connection (e.g., no UML element creation besides what already exists) is required at this stage; only diagrammatic connectivity is needed.
```


In the model merge UI, when you review changes that add a relationship (for example, “package”) to a newly added element, the relationship can end up invisible in the Changes tree or shown in an unexpected place. This happens when the target/referenced element doesn’t yet have an available display name in the merge context—for instance when the referenced element is not part of the change set or its own name change hasn’t been applied yet. Previously the label would fall back to “nameless object,” but that’s confusing and doesn’t help the user understand what relation is changing. Worse, the change might not be nested under the correct parent element node at all, making the tree look like the relation was never added.

Expected behavior:

1. A relation change (add/remove/update) should always appear as an entry under the element that owns it, even if the target element’s display name cannot be resolved. It should not be silently dropped from the tree.
2. When the target name is unavailable, the label should be a simple “Add relation <name>” (or Remove/Update) without the “to …” part, rather than showing “nameless object” which implies a real object exists but is unnamed.
3. The tree-building logic should correctly match relation changes to their parent element regardless of whether the target reference resolves to a known element in the change list.

The overall goal: make the model merge Changes tree reliable and self-explanatory even for relations whose target is still ambiguous or unresolved.


We need a new CSS property `justify-content` to control how a Box distributes vertical space among its children.
It should accept two values: `start` (default) and `stretch`.

When `justify-content: stretch` is set on a Box:

- The children’s heights are stretched to fill the available height (after padding is subtracted).
- Each child initially gets an equal share of the available height.
- If any child has an explicit `min-height` that exceeds its equal share, that child keeps its required height; the remaining space is divided equally among the other children.
- Vertical alignment (`vertical-align`) should still be respected just as it is today, but the total height used for alignment calculation is now the full box inner height (instead of the sum of natural child heights).

The property should follow the same CSS-like style declaration pattern we already use for other enum properties (e.g. `vertical-align`, `text-align`). Make it available in the styling system so it can be set on any Box via its inline style or through style sheets. No parsing changes are needed yet—the enum and style definition are enough.


**Title:** Add missing UML pseudostates to the States toolbox

---

**Problem**
The States toolbox currently only offers tools for *Initial Pseudostate* and a single *History Pseudostate*. UML 2 defines several pseudostate kinds — shallowHistory, deepHistory, join, fork, junction, choice, entryPoint, exitPoint, and terminate — that are not accessible from the toolbox. Users constructing state machine diagrams cannot place these elements via the toolbar, forcing workarounds or manual property edits.

**Request**
Expand the States toolbox section to include a distinct tool for each standard UML pseudostate:

- Shallow History Pseudostate (refining the existing “History Pseudostate” label to avoid ambiguity)
- Deep History Pseudostate
- Join Pseudostate
- Fork Pseudostate
- Junction Pseudostate
- Choice Pseudostate
- Entry Point Pseudostate
- Exit Point Pseudostate
- Terminate Pseudostate

The existing Initial Pseudostate tool should remain. The Transition tool should stay available and be kept in a logical position within the section.

**Behavioral requirements**

- Each tool, when activated and clicked on a diagram, must create a `PseudostateItem` whose underlying model element has the correct `kind` value (e.g., `"join"`, `"fork"`, `"choice"`, etc.).
- The tools should follow the same factory/config pattern already used by the Initial Pseudostate tool: use `new_item_factory` with the appropriate diagram item class and a configuration callback that sets the `kind` and applies the standard `state_machine_config`.
- To reduce duplication, you are encouraged to unify the per-kind configuration callbacks into a single generic function parameterized by the kind string (using `functools.partial` or similar) — but the primary goal is correctness and completeness, not code style.
- Icon names should be chosen following the existing naming convention (e.g., `"gaphor-<kind>-pseudostate-symbolic"`). Placeholder icons are acceptable; the new tools will receive proper artwork later. No keyboard shortcuts are assigned to the new tools (shortcut = `None`).

**Testing**
The existing test that exercises the state machine toolbox actions (e.g., iterates over `state_node_names` to verify tool availability and creation) should be extended to cover every new tool name, ensuring that each added pseudostate kind is properly registered and produces valid model elements.

**References**
- Current toolbox definition lives with the state diagram section; look for the existing `ToolDef` entries for “Initial Pseudostate” and “History Pseudostate” to understand the required structure.
- The `UML.Pseudostate` element uses the `kind` attribute for all pseudostate variants; the valid kind literals are as listed above.


**Request:** When state-machine elements (State, Initial Pseudostate, Final State, History Pseudostate) are dropped on a diagram from the UML toolbox, they should automatically be placed into a `StateMachine` and a `Region`, matching the pattern already used for activity–node containment. Currently they are not container‑namespaced this way.

Behavior:
- If a `StateMachine` already exists in the diagram’s owning package (or at the model root when there is no package), reuse it.
- If that `StateMachine` already has a `Region`, place the new element into that region; otherwise create a default `Region` and assign it to the `StateMachine`.
- If no `StateMachine` exists, create one (and a default `Region`) and place the element there.
- The existing specialisation for initial pseudostate (`kind='initial'`) and history pseudostate (`kind='shallowHistory'`) must still be applied after the container is set up.

Namespacing for state, initial/history-pseudostate, and final-state


**Title:** Undo/Redo does not reverse line-segment splits or merges

**Description:**
Splitting a line segment (clicking on a line to add a new handle) and merging two adjacent segments are not tracked by the undo system. Performing either action and then pressing Undo has no effect; the line remains in its modified state. The expected behaviour is that each segment split or merge is recorded as an undoable action, so that:

- **Undo** reverts the line to its previous segment count and handle positions.
- **Redo** re‑applies the split or merge exactly as it was.

This should work for any line in a diagram (e.g., association, generalisation, or other `LinePresentation` items). The behaviour should be consistent with how moving an individual handle is already undoable.

**Acceptance criteria:**
1. Splitting a segment creates an undo entry; upon undo the line returns to its original segment count and handle positions, and upon redo the split is repeated.
2. Merging two segments does the same: undo restores the split, redo re‑merges.
3. The existing undo/redo of handle positions and other presentation actions remains unaffected.


```
Add an Activity item to the Actions toolbox with grouping support.

- The new item should represent a UML Activity as a rounded rectangle displaying its stereotype and name.
- It should be added as the first tool in the Actions section, before the existing Action tool.
- Implement grouping so that dropping any ActivityNode (e.g., Action, InitialNode, DecisionNode, etc.) onto an Activity adds the node to the Activity's node collection, and removing the node from the Activity reverses the relationship (mirroring the existing grouping behavior between ActivityPartition and ActivityNode).
```


**Title:** Move diagram-scoped action groups off the toplevel window and onto the Toolbox component

**Description:**
Currently when a diagram page becomes active, the `Diagrams` component inserts the page’s action group (prefix `"diagram"`) into the top-level window via `window.insert_action_group("diagram", …)`. This means the toolbox buttons (which trigger actions like `"select-tool"`) depend on the window’s action group map, coupling the toolbox to the window.

We want the Toolbox to own these per-diagram actions. When a diagram tab is focused, `Diagrams` should hand the page’s action group directly to the Toolbox, which will insert it into *its own* container widget under the `"diagram"` prefix. The toolbox buttons will then resolve actions locally without involving the toplevel window. This makes the toolbox more self-contained.

**Acceptance criteria / observable behaviour:**
1. **Toolbox owns diagram actions:** The Toolbox component must provide a way to receive a `Gio.ActionGroup` (or `None` to clear) for the active diagram. It should insert that group into its internal container widget under the `"diagram"` prefix. The component should be able to replace the action group when the active diagram changes.
2. **Diagrams delegates to Toolbox:** In `Diagrams._add_ui_settings`, instead of inserting the action group into the window, give it to the Toolbox (and clear it in `_clear_ui_settings`). The top-level window must *not* receive the `"diagram"` action group anymore.
3. **Tool reset after item placement:** The reset-tool-after-create logic inside `DiagramPage._on_diagram_item_placed` currently contains a GTK3 / GTK4 branch — in GTK3 it activates the action directly on the window's action group, in GTK4 it activates via `view.activate_action`. Since the diagram’s own action group is now always accessible through the diagram widget, this code can be simplified to a single call that looks up `"select-tool"` on the widget’s action group (no version branching needed). The new code should work for both GTK3 and GTK4.
4. **Keyboard shortcuts remain on the window:** Only the `Gio.ActionGroup` moves; the `Gio.ListModel` of keyboard shortcuts must still be added to and removed from the top-level window’s accelerator groups exactly as before. The existing GTK4 `# TODO` placeholders for shortcuts should be preserved.
5. **Constructor change:** The `Diagrams` component now requires a reference to the `Toolbox` instance; update its constructor signature and all places where `Diagrams` is instantiated so that a `Toolbox` is provided.

**Non-goals / constraints:**
- Do not change the toolbox button creation or the action names (`"select-tool"`, etc.).
- Do not change how the action group itself is built – only where it is inserted.
- The GTK4 code for shortcuts is not yet implemented; keep the existing `# TODO` comments and `pass` blocks unchanged.
- No changes to the top-level menu or application actions are expected.


**Title:** Swap generalization arrow direction to match UML conventions

**Description**

In Gaphor, when a user draws a Generalization relationship between two classifiers (classes, interfaces, etc.), the hollow triangle arrowhead that marks the *general* (parent) end currently appears on the wrong end of the line. Standard UML tools expect that dragging from the *specific* (child) end to the *general* (parent) end places the triangle on the target – the *general* end. Gaphor currently does the reverse: if you drag from specific to general, the triangle ends up at the starting point (the specific end). This is confusing for users familiar with other modeling tools.

We need to swap the mapping of the line’s head/tail handles to the `UML.Generalization.general` and `UML.Generalization.specific` properties so that:
- The handle where the drag *ends* (the “tail” handle, typically the last handle created) becomes the **general** end (the one that gets the triangle).
- The handle where the drag *starts* (the “head” handle) becomes the **specific** end.

**Backward‑compatibility constraint**
This change must also be applied retroactively to model files saved by earlier versions (before 2.6.0). When loading such a file, the stored `head-connection`/`tail-connection` references still use the old mapping. If we simply change the runtime logic without a migration, existing diagrams would have their generalization arrows visually reversed. Therefore, the file‑loading process needs an upgrade step that *swaps* the `head-connection` and `tail-connection` references for `GeneralizationItem` elements whenever the file’s Gaphor version is older than 2.6.0. Newly saved files will use the new mapping and do not need to be touched again.

**Acceptance criteria**
- Creating a new Generalization by dragging from a specific class to a general class places the triangle arrowhead on the general class.
- Opening a model that was saved with an older Gaphor version (e.g. 2.5.0) preserves the visual layout – generalization triangles remain on the same classes the user originally placed them on.
- Newly saved models use the corrected mapping.


**Title:** Dropping a ConnectableElement on a lifeline does not assign the `represents` association

**Description**
In an interaction diagram, dragging an `Interface` (or any `ConnectableElement` such as `Property` or `Parameter`) from the model browser onto a lifeline currently creates a new generic diagram box for that element rather than linking it to the lifeline.

According to the UML metamodel used by Gaphor, a `Lifeline` has a `represents` property of type `ConnectableElement`. The expected behavior is:
- If the user drops a `ConnectableElement` directly onto a lifeline shape, that element should become the lifeline’s `represents` value, and no new diagram item should be created.
- If the drop location does **not** hit a lifeline, the existing behavior (creating a diagram item for the dropped element) should be preserved as a fallback.

**Steps to reproduce**
1. Create an interaction diagram with a lifeline.
2. In the model browser, create an `Interface` (or any `ConnectableElement`).
3. Drag the `Interface` from the model browser and drop it onto the lifeline shape in the diagram.

**Observed behavior**
A new `Interface` diagram box appears on the diagram; the lifeline’s `represents` association remains unchanged.

**Expected behavior**
The lifeline’s `represents` property should be set to the dropped `Interface`, and no additional diagram item should be created.

**Additional notes**
- Only `ConnectableElement` subtypes (including `Interface`, `Property`, `Parameter`) should trigger this assignment.
- The hit-test should use the same spatial detection that the lifeline already uses for other interactions (i.e., the drop is considered “on” the lifeline only when the drop coordinates lie within the lifeline item’s sensitive area).
- If the lifeline’s underlying model element (`subject`) is missing, or if no lifeline is found at the drop position, the normal element‑dropping logic (creating a diagram item) should apply.

This change would make it possible to quickly set a lifeline’s type by dragging from the model browser, matching the intuitive expectation of model-to-diagram drag‑and‑drop in Gaphor.

Allow to drop a ConnectableElement on a lifeline


Make `StateMachine` and `Region` diagram items accept dropping a state or pseudostate onto them (i.e., support `group`/`ungroup`). Dropping a `Vertex` onto a `StateMachine` should place it inside the state machine's region, creating a default `Region` automatically if none exists. Dropping a `Vertex` onto a `Region` should place it directly in that region. Ungrouping should clear the vertex's container when the vertex is a direct child of that region. The state machine diagram item should also update its visual bounds when children are added or removed.

Allow grouping of states in state machines and regions


**Derived unions sometimes return an internal `unioncache` wrapper instead of the expected collection**

When accessing a derived union property (e.g. `ownedElement`) on a model element, callers may receive a `unioncache` object rather than a plain list or set. This breaks code that expects a standard collection interface and can surface as an assertion like `assert isinstance(result, list)`.

The problem occurs because the derived union computation iterates over *all* declared subsets, including those inherited from a metaclass or parent class that are only present through a `redefine` descriptor but are not actually owned by the concrete element’s type. When such a non‑owned subset is invoked on the element, it can yield another `unioncache` (since the derived cache is installed per‑type), leading to a nested‑cache result.

**Desired behaviour:**
- When a derived union collects values from its subsets, it must skip any subset that is not defined on the element’s own class (resolving `redefine` chains to the original property before checking ownership).
- A derived union should never return an `unioncache` as its direct value; the result must always be the final unwrapped collection (list or set).

Ensure derived unions only traverse properties owned by the object


Do not include `ActivityParameterNodeItem` visuals when copying diagram contents.

When copying activity diagram elements (e.g., via copy/paste), `ActivityParameterNodeItem` instances are currently being included in the copy data alongside their underlying model elements. These visual items are supposed to be created automatically from the `Activity` parameters during paste/model loading; copying them explicitly leads to duplicate items or incorrect diagram state. The copy operation should be updated to skip `ActivityParameterNodeItem` items entirely, so that only the model-level `ActivityParameterNode` and `Parameter` are copied.


**Title:** Drag-and-drop in model browser can appear to succeed without changing ownership

**Problem**
When I drag an element in the model browser (the namespace tree) to a new parent, sometimes the operation visibly completes—the tree may briefly rearrange or a “moved” notification appears—but the element’s actual owner remains unchanged. This leaves the user confused because the UI suggests a successful move when nothing meaningful happened.

**Expected behavior**
The model browser should only reflect changes to the owner relationship. If the drag target cannot become the new owner of the element (or the owner does not actually change after the drop), the entire change should be discarded: no modification to the model, no rearrangement in the tree, and no notification suggesting a successful move. The user should only see a successful result when the element’s owner really transitions to the new parent.

**Reproduction steps**
1. Open a model with at least two packages and a class inside one of them.
2. In the model browser, drag the class and drop it onto a package that cannot own a class (e.g., a property, or a context where `can_group` returns false but the drop handler still attempts to group).
3. Observe: the tree may briefly update, or a notification appears stating the element was dropped, yet the element remains under its original owner.
4. Sometimes a grouping relationship unrelated to ownership is modified even though ownership is unchanged.

**Additional context**
This seems to happen because the drop handler noisily performs a grouping/ungrouping operation without checking whether the owner actually changed afterwards. If ownership cannot be changed, the handler should roll back the entire transaction and remain silent, so the model browser behaves as a strict reflection of owner relationships only. Side effects to non-owner relationships should not leak through.

**Impact**
This confuses users, especially those who rely on the model browser as a hierarchical view of ownership. Silent side effects can also corrupt the model by creating unintended associations.

Only allow ownership to change in namespace model


As a maintainer, I'd write:

---

**Association creation should match the behavior of other relationship connectors**

Associations currently behave inconsistently compared to other diagram relationships (e.g., `InterfaceRealization`, `Dependency`). The connectors for those relationships:

- Only create model elements once both ends are connected.
- Look for an existing relationship between the two elements and reuse it, even across diagrams.
- Copy properties (name, navigability, aggregation, etc.) when reconnecting an existing association to a different element.

`AssociationConnect` does none of these. Instead, it creates model elements eagerly, does not reuse existing associations, and loses properties when an association end is reconnected.

The goal is to bring association creation in line with the other relations. Specifically:

1. **Model elements only when both ends are connected.** Don't create member ends or the association itself until both ends have been dropped on classifier shapes.
2. **Reuse existing associations.** When connecting the same two classifiers (in any order) that already have an association, reuse that association, even if it is shown in another diagram. A new association should only be created if no matching one exists.
3. **Preserve properties on reconnect.** When an association end is moved to a different classifier, the new association should inherit the name, navigability, aggregation, etc., from the original association (as if the user copied it). The original association should be cleaned up.

The behavior should be implemented in a way that remains compatible with the base connector logic in `UnaryRelationshipConnect`, without breaking other relationship types.

Create associations in line with other relations


The `Dependency` model class has been moved from `UML.Dependency` to `gaphor.core.modeling.Dependency`. All production code that creates or uses Dependency instances—for example, the dependency diagram item, its property page, and any other direct references—must now import and use the core class. Additionally, the textual description format for dependencies (which currently yields strings like “supplier: {name}”) should now be handled by the core formatting system in `gaphor/core/format.py` via a singledispatch registration for the new core Dependency type, and any UML‑specific registration for dependencies should be removed. The observable behaviour of creating, connecting, and displaying dependencies must not regress.


**Title:** Code generator ignores explicit enum default values; UML/SysML model defaults are wrong

When a `UML.Class` declares an attribute typed by an `Enumeration` and provides a default value, the `gaphor.codegen.coder.variables` function always uses the first literal of the enumeration as the default in the generated `_enumeration(...)` call. The intended default from the model is lost.

This causes the generated model definition files (`gaphor/UML/uml.py` and `gaphor/SysML/sysml.py`) to contain wrong defaults for several enumeration attributes. For example:
- `ObjectNode.ordering` should default to `"FIFO"` but currently defaults to `"unordered"`.
- `Parameter.direction` should default to `"in"` but currently defaults to `"inout"`.
- `FlowProperty.direction` should default to `"inout"` but currently defaults to `"in"`.

**Root cause 1:** `variables()` in `gaphor/codegen/coder.py` unconditionally picks `a.type.ownedLiteral[0].name` as the default string. It should use the attribute’s `defaultValue` (when it is a meaningful `LiteralString`) and fall back to the first owned literal only when no explicit default is set.

**Root cause 2:** `create_value_specification_for_type_and_value()` in `gaphor/UML/recipes.py` only creates value specifications for a fixed set of types (bool, int, str, etc.). For any other type (including enumerations), it returns `None`, which means the attribute’s default value is silently dropped during model loading or attribute resolution. This function should handle “unknown” types by storing the value as a `LiteralString` so that the default is preserved and can later be read by the code generator.

**Request:**
1. Modify the enumeration handling in `variables()` so that when an attribute has an explicit default value stored as a `LiteralString`, that value is used as the third argument to `_enumeration`. Keep the fallback to the first owned literal for attributes without an explicit default.
2. Extend `create_value_specification_for_type_and_value()` to cover cases where the type is not one of the pre‑defined ones. In those cases, create a `LiteralString` that captures the string value and its name.
3. After the generator fix, regenerate the `uml.py` and `sysml.py` model definitions so that the enumeration default arguments match the UML/SysML specifications (the examples above should be fixed; any other enumeration defaults should be reviewed for correctness).

Set enumeration default values defined in the model


In `gaphor/ui/namespace.py`, the `NamespaceModel` class acts as a wrapper around an underlying `Gtk.TreeModel`. Currently, it exposes several methods (`get_iter_first`, `get_path`, `get_value`) that are just one-liners forwarding directly to the wrapped model. These thin wrappers serve no abstraction purpose — they leak GTK implementation details and encourage callers to treat `NamespaceModel` as if it were a bare `TreeModel`.

We should clean this up. The only external retrieval needed from the tree model is getting the actual element stored in a row. Since elements are always stored in column zero, introduce a proper `get_element(iter)` method that returns the element for a given tree iterator. Remove the three forwarding methods (`get_iter_first`, `get_path`, `get_value`).

Callers (including tests) should use `NamespaceModel`'s existing `iter_children(None)` where they previously used `get_iter_first()`, and use the new `get_element()` where they previously used `get_value(iter, 0)`. The `iter_for_element` method already exists and returns a tree iterator — pairing it with `get_element` should be sufficient for navigating and inspecting the model.

One small side cleanup: the `_visible` method has a docstring comment `"""Special case: Non-navigable properties."""` that doesn't align with what the method actually does. Remove that comment while touching the file.

Clean up forwarded methods in NamespaceModel


Subject: Query processing overhead causes noticeable lag during `gatsby develop` on large sites

I maintain a site with ~3000 pages and a significant number of static queries. In development, after editing a page or query, the rebuild and hot reload can take several seconds longer than I would expect from the number of queries actually re-run. The terminal shows batches of queries, but the batch processing itself appears to impose its own delays beyond the pure graphql execution time.

Using `GATSBY_EXPERIMENTAL_QUERY_CONCURRENCY=8` helps slightly, but the overall throughput plateaus and the CPU utilisation remains well below the available cores. Profiling with the Node.js inspector reveals that a meaningful fraction of the query processing time is spent inside the queue machinery (`better-queue` and its custom in-memory store), not in the actual query runners. The custom `memoryStoreWithPriorityBuckets` and the event‑handling overhead in `better-queue` seem to add per‑job latency that becomes significant when hundreds of queries need to be processed.

I expect query execution to be limited primarily by the concurrency of graphql execution (as configured) and by the speed of the graphql runner itself. The queue should introduce as little overhead as possible so that the system can keep all workers saturated and complete the entire query batch in minimal time.

This overhead makes incremental builds feel sluggish and slows my development feedback loop. It would be a big quality‑of‑life improvement if the internal query‑processing pipeline were optimised to reduce the overhead of job scheduling and coordination, ideally by using a simpler, higher‑performance queue implementation that does not require a separate persistent store or complex priority logic.

The fix should:
- Maintain the current configurable concurrency limit via the environment variable.
- Ensure that a single failing query still causes the batch to fail as before.
- Handle the development‑specific notification of static query results to the websocket manager exactly as today (no regressions in the dev experience).
- Not introduce changes in the order in which queries are processed beyond what is necessary for performance (FIFO is acceptable; the previous priority‑based ordering was not critical).

perf(gatsby): use fastq instead of better-queue + refactor (#31269)


### Bug Report: gatsby-source-wordpress fails to process images whose URLs contain special characters (e.g., `©`)

#### Observed behavior
When a WordPress post contains an `<img>` tag with a `src` that includes characters like `©` (for example `/wp-content/uploads/2020/01/©SDM-Yep-©Hi-000-Header.jpg`), the plugin does not detect the image as a remote media item. The image is left as-is in the final HTML, never downloaded, and never transformed into a local Gatsby Image node.

#### Expected behavior
Any `<img>` tag whose `src` points to a supported image extension (`jpg`, `png`, `gif`, `webp`, etc.) should be recognized regardless of the characters in the path segment, as long as the URL is valid and the characters do not break HTML parsing. The image should be fetched, stored locally, and replaced with a Gatsby Image / static file reference just like images that contain only ASCII alphanumerics and common punctuation.

#### Impact
- Images with special/uncommon characters in filenames are broken in production — they remain as remote URLs that may not load, especially if the WordPress site is not publicly accessible from the frontend.
- Users with internationalized content or filenames containing characters like `©`, `®`, accented letters, or other Unicode symbols cannot benefit from Gatsby’s image optimization pipeline for those assets.
- The regex responsible for matching `src` attributes in `<img>` tags is too restrictive: the old character class did not allow many valid URL characters, causing missing matches and inconsistent behavior.

#### Additional constraints / edge cases
1. **JSON-encoded strings** – When the image URL appears inside a JSON-encoded field (e.g., `\\/\\/` escaping), it should be skipped to avoid corrupting serialised data.
2. **Content inside `<pre>` and `<code>` tags** – Images nested within these elements should be ignored, as they are likely code examples or snippets, not actual media to fetch.
3. **Partial URLs** – Image `src` values that are absolute paths (starting with `/`) as well as full `http://` or `https://` URLs must be supported.
4. **Query strings** – URLs with query strings (`?` followed by parameters) should still be matched, but the dot in the query string pattern must be escaped to avoid unintended wildcard matching.

#### Relevant area
The image-matching logic resides in the `process-node` step of `gatsby-source-wordpress`. The current regex is defined inline and used to extract all image URLs from a node’s string content before batch-fetching the corresponding media items. A more permissive regex that matches any character except quotes and whitespace for the URL path is needed, while preserving the existing filters for JSON and `<pre>`/`<code>` exclusion.

fix(gatsby-source-wordpress): HTML image regex's (#29778)


# fetch‑remote‑file: preserve cached file path across builds when server returns 304

## Problem

When `fetchRemoteFile` receives a `304 Not Modified` response during a build, it uses the (empty) response body to compute a content digest.  That digest can differ from the one computed when the resource was originally fetched with `200 OK`.  Between builds (different `buildId`) the resulting filename therefore changes, even though the remote resource has not changed.  This causes:

- a cache miss for the already‑downloaded file
- unnecessary I/O (the tmp file may be moved under a new name)
- inconsistent file paths for the same resource across builds and workers

## Desired behaviour

- When a remote resource is first fetched (`200`) we must store both the response headers (e.g. `etag`) **and** the content digest in the header cache entry.
- When a subsequent `304` occurs we must reuse the previously stored digest to construct the file path.  This means the filename stays the same as the original download.
- The fix must work across multiple builds (different `__GATSBY__.buildId` values) and across worker processes, as long as the shared cache is used.

## Concrete ask

Update the header‑caching logic in `packages/gatsby-core-utils/src/fetch-remote-file.ts`:

1. Change the value stored under `cacheIdForHeaders(url)` from a plain headers object to an object containing `{ headers, digest }`.
2. When a `200` response is received, store the digest together with the headers.
3. When constructing the final filename, prefer the `digest` from the cached header entry (if present) over the digest computed from the current response body.  This ensures that a `304` response leads to exactly the same file path as the original `200` download.

No behavioural change is expected for first downloads or when caching is disabled; the only affected scenario is a `304` response in a build that reuses a cache populated by a previous successful download.

fix(gatsby-core-utils): handle 304 correctly between builds (#33975)


## Bug Report: LMDB databases left open after test suites cause file-lock errors on Windows

### Summary

Running the Gatsby test suites that exercise the LMDB-based cache (`packages/gatsby/src/utils/cache-lmdb.ts`) occasionally leaves the underlying LMDB environment open. After the suite finishes, the test harness tries to delete or re-create the cache directory (`.cache/caches-lmdb-*`) but fails because the database files remain locked by the owning process. This is most visible on Windows CI, where file locking is stricter, but it can affect any platform if a handle is not released.

### Observed Behavior

- `beforeAll` in cache tests empties the cache directory (`fs.emptyDir`) before each suite to start from a clean state.
- The current cleanup relies on deleting files, but **never closes the LMDB root store** that was opened by a previous suite (or by a parallel worker using the same directory).
- Because LMDB holds an exclusive lock on the data file, the deletion or moving of those files is denied with `EPERM` (operation not permitted) or similar errors.
- This leads to flaky test failures that are hard to reproduce locally but trigger frequently in CI pipelines, especially Windows runners.

### Expected Behavior

- Every test suite that opens an LMDB root database must ensure that the database is properly closed and any file locks are released **before** the suite’s teardown.
- After each suite finishes, no dangling handles should remain on the `.cache/caches-lmdb-*` directory, so it can be safely cleaned up or reused.
- The tests should not need to rely on the OS eventually releasing the locks; cleanup must be deterministic and synchronous with suite boundaries.

### Impact

- Flaky CI failures due to `EPERM` when trying to empty or remove the cache directory.
- Wasted developer time investigating non-deterministic Windows-only failures.
- Potential for cascading failures when one suite’s leftover lock blocks subsequent suites or worker processes from accessing the cache directory.

### Implementation Hints (for context, not prescription)

The fix likely requires:

1. **Centralized tracking of open LMDB root stores.** The cache module should keep a global registry of opened databases so they can be closed in a group at teardown.
2. **A reset/teardown function** that closes the root store, clears the registry, and ensures the directory is ready for the next suite.
3. **Automatic invocation at suite teardown.** This could be achieved via a custom Jest environment (teardown hook) or a consistent `afterAll` block in the test files. The chosen mechanism must not rely on individual test authors remembering to add cleanup.
4. **Safety on concurrent access.** The solution should avoid race conditions when multiple test workers might be operating on the same cache directory (though ideally each worker has its own directory).

The goal is that after **every** test suite, any open LMDB root databases for the process’s cache directory are closed, and the directory is emptied, making the system robust against file-locking issues across all platforms.

fix(test): clear and close lmdb after each test suite (#36343)


Improve node creation performance in `gatsby-source-filesystem` by eliminating the `JSON.parse(JSON.stringify(...))` round‑trip in `createFileNode`. Construct the file node object directly, and convert all `Date` values (from `fs.Stats`, like `mtime`, `atime`, `ctime`, `birthtime`, and the corresponding lowercase aliases) to ISO‑8601 strings so the node remains JSON‑safe for Gatsby’s data layer. No other property names or values should change.

perf(gatsby-source-filesystem): dont JSON parse/stringify the node (#27597)


**Summary**
When a site uses a `pathPrefix` together with a `trailingSlash` option of `"never"`, the root path (`/`) still receives a trailing slash after the prefix is applied. The `trailingSlash` setting appears to be applied *before* the prefix is added, so the final URL does not respect the chosen trailing slash policy.

**Steps to reproduce**
1. Configure Gatsby with `pathPrefix: '/blog'` and `trailingSlash: 'never'`.
2. Use `<Link to="/">` anywhere in the site.
3. Inspect the generated `href` attribute.

**Expected behavior**
The link should produce `/blog` – no trailing slash, because `trailingSlash: 'never'` must suppress the slash after the complete path (prefix + path).

**Actual behavior**
The link renders as `/blog/` (trailing slash present). The trailing slash behavior is applied to the original path segment before prefix is prepended, so the final URL loses the intended `"never"` constraint.

**Additional context**
- This also misbehaves with `trailingSlash: 'always'` in subtle ways: the trailing slash should be enforced on the *final* pathname, not only on the path before prefixing.
- The issue affects absolute paths; relative paths do not go through prefix logic and are unaffected.
- The `pathPrefix` value should be combined with the path first, and only then should the `trailingSlash` option decide the final format (with or without trailing slash).

**Impact**
Sites that rely on both `pathPrefix` and a non-legacy `trailingSlash` option (`always`/`never`) can end up with inconsistent or incorrect URLs, breaking canonical expectations and potentially causing unexpected redirects or duplicate content issues.

fix(gatsby-link): Correct handling of trailingSlash & pathPrefix (#36542)


We need to remove the `fs` import from the SSR entry (`gatsby-ssr.js`) of `gatsby-plugin-manifest`.
Currently `onRenderBody` calls `fs.readFileSync(pluginOptions.icon)` to compute a digest for cache busting. This makes the SSR bundle depend on the filesystem, which is problematic in environments where `fs` isn’t available (e.g. some serverless runtimes) and unnecessarily couples the rendering path to Node built-ins.

The ask:

1. **Move filesystem work to build time.**
   Compute the cache-busting digest during a Gatsby Node API that runs at build (e.g. `onPreInit`) instead of inside `onRenderBody`. Store the result on the plugin options object so it can be passed through to the SSR entry without further `fs` calls.

2. **Consolidate default option handling at build time.**
   Right now `onRenderBody` repeats default-value logic for `legacy`, `cache_busting_mode`, `include_favicon`, and `theme_color_in_head`. Set these defaults once in a build-time hook (e.g. `onPreInit`), so the SSR code can simply read the already‑normalised values.

3. **Keep the SSR function pure.**
   After the change, `gatsby-ssr.js` must not import `fs` or `createContentDigest`, and should use the pre‑computed `cacheDigest` option directly for cache busting. The existing behaviour for cache busting modes (`none`, `query`, `name`) must be preserved; if no icon is provided, the digest should be `null` and no link tags should be affected.

4. **No breaking API change.**
   The user‑facing plugin options remain the same; only internal implementation moves. The plugin should continue to inject the same link/meta tags (favicon, manifest, theme-color, legacy apple-touch-icons) with correct cache busting, but without touching the filesystem during SSR.

fix(gatsby-plugin-manifest): remove fs from ssr (#24097)


We need to replace the `mitt` event emitter used for dispatching Gatsby internal actions with a custom emitter that avoids expensive array splices during handler removal.

**Background**
The module `packages/gatsby/src/redux/index.ts` exports an `emitter` created from the `mitt` package. That package stores handlers per event in a plain array and uses `Array.splice` when a handler is removed. During large-scale builds, many handlers are added and removed, causing splice operations to become a performance bottleneck—some profiles show ~25% of bootstrap time is spent here.

**Requirements**

- Create a new emitter factory function (e.g., `mett`) in `packages/gatsby/src/utils/mett.ts` that replaces `mitt` with no external dependencies.
- The emitter must expose the same interface:
  - `on(eventName: string, callback: (payload: any, eventName: string) => void): void`
  - `off(eventName: string, callback: (payload: any, eventName: string) => void): void`
  - `emit(eventName: string, payload: any): void`
- Internally it should use `Map` and `Set` so that adding and removing handlers is constant/sub-linear time, even with thousands of handlers.

**Behavioral invariants**

1. **FIFO order for a single event:** When multiple handlers are registered for the same event name, they must be called in the order they were added (last-added = last-called), even after some are removed.

2. **Wildcard (`*`) handlers:**
   - Handlers registered with the event name `'*'` are invoked for **every** emitted event.
   - Star handlers must be called **after** all specific handlers for that event have finished.
   - Among themselves, star handlers also execute in FIFO order, independent of specific handlers.

3. **Deduplication:** Adding the same handler function multiple times for the same event name should only result in one call per emit. (The emitter should not call the same listener more than once for a single event dispatch.)

4. **Removal:**
   - Calling `off(eventName, callback)` removes all registrations of that callback for the given event name, regardless of how many times it was added.
   - Removing a handler that was not registered for the event must not throw, and must not affect other handlers.
   - It must be possible to add, remove, and re-add the same handler without duplication; after a subsequent `off` the handler should no longer be invoked.

5. **Cross-event isolation:** The same handler function may be registered for different event names independently; adding/removing it for one event must not affect its registration on another event (or on `*`).

6. **No-op safety:** `off` on a non-existent event name must not throw.

**Adoption**

- In `packages/gatsby/src/redux/index.ts`, replace the `mitt()` import with the new `mett()` factory and export it as the action emitter.
- Remove `mitt` from project dependencies.
- Ensure that any integration or unit tests that directly instantiate `mitt` are updated to use the new factory (the semantics should be equivalent).

**Target outcome**
Bootstrap performance improves at scale, with handler removal no longer contributing significant cost.

perf(gatsby): replace `mitt` with a modern Map/Set based version of it (#23223)


We need Gatsby to be resilient against Parcel segfaults when compiling gatsby-node.ts / gatsby-config.ts. The fix should move Parcel’s compilation into a short-lived worker process to isolate crashes and automatically retry with cache clearing.

- Run Parcel compilation inside a `gatsby-worker` child process so that a hard crash (segfault) does not bring down the main Gatsby process.
- If the worker fails **without** returning `diagnostics` (i.e., the failure is not a regular TypeScript/parcel compilation error), treat it as a transient crash:
  - Delete the parcel LMDB cache directory (`.cache/.parcel-cache`) to clear any corrupt state.
  - Retry the compilation, up to a fixed limit (e.g., 5 retries), using exponential backoff between attempts.
- If the worker returns **with** `diagnostics`, handle them exactly as today: report the errors and stop the build (no retry).
- Because `bundleGraph.getBundles()` returns objects that are not serializable across worker boundaries, the worker must return a plain array of `{ filePath, mainEntryPath }` extracted from the bundles.
- Ensure the LMDB cache is properly closed/removed between retries, and tolerate `EBUSY` errors on Windows when deleting the cache directory.

This change should make Gatsby builds self-healing after the common “parcel segfault” failure, eliminating the need for users to manually delete `.cache`.

fix(gatsby): try to automatically recover when parcel segfaults (#38773)


Currently, the production page renderer (`public-page-renderer-prod`) expects callers to supply a `pageResources` prop. This breaks self-containment and can lead to missing or stale resources in certain navigation scenarios. The renderer should internally fetch the needed resources, matching the self-loading behavior already present in the development renderer (`public-page-renderer-dev`).

**Acceptance criteria**
- `ProdPageRenderer` must **not** accept a `pageResources` prop.
- On every render, given a `location` object, the component must:
  1. Call `loader.loadPageSync(location.pathname)` to synchronously obtain the page resources for the current path.
  2. If the returned resources are falsy, render `null` (return nothing).
  3. If resources are found, pass them to `InternalPageRenderer` for normal rendering.
- The loader module is already present in the project; `ProdPageRenderer` must import it directly.

This makes the production page renderer self-sufficient and prevents scenarios where the caller might provide incorrect or unavailable page resources, while keeping the synchronous loading path consistent with the existing development renderer.

fix(gatsby): Load resources in ProdPageRenderer (#26092)


We need to improve the i18n utility functions in `www/src/utils/i18n.js` and add proper unit tests. Currently they are tightly coupled to `process.env.LOCALES` and have a few edge-case bugs. This ticket tracks the necessary changes.

**Requirements:**

1. **Make `getLanguages` pure and stricter.**
   - Accept a locale string (like `"es ja pt-BR"`) as a parameter so it can be tested without environment variables.
   - If the string is empty/falsy, return an empty array.
   - Throw an error if the string contains the default locale (`"en"`) – the default locale should never be listed in `LOCALES`.
   - Throw an error if any code in the string does not appear in the language list from `www/i18n.json` (the error message should reference `i18n.json`).

2. **Fix `localizedPath` to correctly handle already-localized paths.**
   - The current logic (`langCodes.includes(base)`) treats any valid locale prefix as an already-localized path and returns it unchanged. This breaks when the path is already prefixed with a *different* locale (e.g., `localizedPath("ja", "/es/docs/")` should return `/ja/es/docs/`, but today it returns `/es/docs/`).
   - Instead, only consider the path already-localized if its first segment equals the requested `locale` parameter.

3. **Make `getLocaleAndBasePath` accept an explicit language code list.**
   - Add an optional second parameter (defaulting to the codes from `process.env.LOCALES`) so callers can pass a custom set of valid locales for testing or other contexts.

4. **Export `getLanguages`** so it can be used in tests (currently only `langCodes`, `langs`, `defaultLang`, `localizedPath`, and `getLocaleAndBasePath` are exported).

5. **Add unit tests** covering all exported functions, including edge cases:
   - Empty input, valid locale lists, default-locale-in-list, invalid locale.
   - Default and non-default locale path prefixing, and paths that already start with the target locale.
   - Paths with and without a locale prefix, using custom code lists.

The implementation should keep backward compatibility: the existing module-level `langs` and `langCodes` are still derived from `process.env.LOCALES` as before, and `localizedPath`/`getLocaleAndBasePath` behave the same for typical calls (except the bug fix mentioned).

chore(www) Add unit tests for i18n utils (#21941)