As a maintainer, I would file a ticket like this:

---

**Title:** SA1502 code fix missing for method-like declarations, enum declarations, and namespace declarations

**Context:**
Our `SA1502ElementMustNotBeOnASingleLine` analyzer already reports violations when method bodies (`Block` syntax), enum declarations, and namespace declarations are written on a single line. However, `SA1502CodeFixProvider` only handles the property-like case (`AccessorList`) and leaves the other three `SyntaxKind` branches as placeholder TODOs. Users get a diagnostic for these elements but no offered fix, which is inconsistent and a common complaint.

**What we need:**
Implement the code fix for the three remaining cases:

1. **Method-like declarations** – methods, constructors, destructors, etc. that contain a `Block`. The diagnostic is raised on the block node, so the code fix must handle `SyntaxKind.Block` and reformat the enclosing `BaseMethodDeclarationSyntax` (or its body block). The fix should expand a single-line block like `void Foo() { /* code */ }` into a multi-line layout, preserving trivia.

2. **Enum declarations** – `SyntaxKind.EnumDeclaration`. The fix should take `enum E { A, B }` and expand to multiple lines with members indented.

3. **Namespace declarations** – `SyntaxKind.NamespaceDeclaration`. Similar expansion.

All three cases should reuse the existing `ReformatElement` helper that already works for accessor lists. It accepts a syntax root, the syntax node containing the braces, the open-brace token, and the close-brace token, along with indentation options. The new branches need only call `ReformatElement` with the appropriate parameters.

**Edge cases and constraints:**
- The `ReformatElement` helper currently assumes the start-of-content token (first token after `{`) and end-of-content token (last token before `}`) are always distinct. When the block contains exactly one token (e.g. `{ }`, `{ Value1 }`, or `{ using System; }`), those two are the same token and the existing dictionary modification throws a `KeyNotFoundException` because it tries to add the same key twice. The fix must safely handle this: when encountering a single-token block, update the trailing trivia of the already-stored token rather than adding a duplicate entry.
- Trivia (comments, whitespace) around the braces and content must be preserved and correctly repositioned. For example, `Foo() /* T1 */ { /* T2 */ bar; /* T3 */ } /* T4 */` should expand without losing any trivia.
- The behavior must match what we already ship for property accessors: braces go on their own lines, inner content gets an extra indentation step relative to the parent.

**Acceptance criteria:**
- Applying the code fix to a single-line method/constructor/destructor/enum/namespace that violates SA1502 produces a well-formatted multi-line expansion.
- Empty blocks (`{ }`) expand to `{` + newline + `}` with no extra content lines.
- Blocks with a single statement/member/using expand correctly.
- Both “everything on one line” and “open brace on its own line but content and close brace on the next line” cases are handled identically.
- Trivia is never dropped and appears in a reasonable order (open brace trivia -> content trivia -> close brace trailing trivia).

Added codefix (+ codefix tests) for method like declarations, enum declarations, and namespace declarations


**Title:** SA1135: false positives and crashes with nullable types, unbound generics, global-qualified arguments, and global-namespace types in `using` alias directives

**Problem:**
`SA1135` (“Using directives must be qualified”) does not handle several edge cases when the using alias directive involves:

- nullable value types (e.g. `using Example = List<int?>`)
- omitted generic type arguments (e.g. `using Example = List<>`)
- global-qualified type arguments (e.g. `using Example = List<global::System.Int32>`)
- types defined directly in the global namespace (e.g. `using Example = MyClass` with `class MyClass` in no namespace)

In some of these cases the rule may throw a `NullReferenceException`; in others it may incorrectly report that the directive is not fully qualified when it actually is, producing a false positive.

**Expected behavior:**
When computing the canonical qualified form for comparison, the implementation must:

- represent `Nullable<T>` as `T?` (the C# nullable value type syntax)
- skip omitted type arguments entirely (the canonical form should simply omit them)
- preserve `global::` qualification when it appears in type arguments
- treat types from the global namespace as correctly qualified (no namespace prefix is needed)
- avoid null-reference failures when symbols or syntax nodes are unavailable for any part of the type (for example when a generic type argument is omitted or unresolvable)

After the fix, those `using` alias directives should produce no SA1135 diagnostic (excluding genuine compiler errors such as CS7003 for an unbound generic name in source, which is not the rule’s responsibility).

Additional tests and fixes for qualified using directives


SA1208 (`SystemUsingDirectivesMustBePlacedBeforeOtherUsingDirectives`) should also enforce ordering of using directives inside file-scoped namespace declarations (the `namespace Name;` syntax introduced in C# 10). Today, when a file declares a file-scoped namespace and contains using directives, SA1208 does not report violations even when `System.*` using directives appear after other using directives. The rule already works correctly for using directives inside compilation units and traditional block-scoped namespace declarations, and it should behave the same way for file-scoped namespaces.

Add file-scoped namespace support to SA1208


```
The StyleCop rule SA1518 (CodeMustNotContainBlankLinesAtEndOfFile) has two issues:

1. The analyzer reports false positives for files that end with a comment character (`//`) or a preprocessor directive such as `#endif`. These endings are valid content, not blank lines, so no diagnostic should be reported.
2. When the SA1518 code fix is applied, it strips more trivia than necessary: it removes the comment or directive along with the trailing newline, corrupting the file. The fix should only remove the offending trailing blank lines while leaving all other content (comments, directives, a single final newline) intact.

In addition, the rule currently doesn’t handle files using Unix line endings (LF only) consistently. A single LF at end of file should not be flagged (mirroring the existing behaviour for a single CRLF). Multiple trailing LF-only blank lines should be flagged.

Acceptance:
- SA1518 must not fire when the last meaningful line contains a comment (`//`) or a directive (`#endif`).
- The SA1518 code fix must preserve such comments/directives and only delete the trailing blank lines.
- A single final newline (CRLF or LF) must not be reported; multiple final newlines (CRLF, LF, or mixed) must be reported.
- Trailing whitespace on the last line (e.g., spaces after the last non-whitespace character, followed by additional newlines) should still be treated as a trailing blank line and reported.
```

Incorporated suggested changes for pull request #502


**Request: Extend SA1111 to flag closing-parenthesis placement errors in local function declarations**

SA1111 (“Closing parenthesis must be on line of last parameter”) currently checks method declarations, constructors, indexers, operators, invocations, and object creations, but it ignores local function declarations (C# 7 local functions). As a result, if the closing parenthesis of a local function’s parameter list is placed on its own line, no diagnostic is produced and no code fix is offered.

We need SA1111 to treat local function parameter lists the same way it treats ordinary method parameter lists:
- When the local function has at least one parameter and its closing parenthesis is on a different line than the last parameter, produce the SA1111 diagnostic at the closing parenthesis.
- When the local function has no parameters, do **not** report a diagnostic even if the closing parenthesis is on a line by itself.
- When the closing parenthesis is on the same line as the last parameter, do **not** report a diagnostic.
- The existing code fix for SA1111 (moving the closing parenthesis to the last parameter’s line) must also work for local functions.

The implementation must follow the pattern already established in the codebase for supporting newer C# syntax on older Roslyn tooling: treat local function syntax via a lightup wrapper (the same approach used by the sibling rule SA1110, which already handles local functions for the opening-parenthesis rule). Do not introduce a direct compile-time dependency on the C# 7 `LocalFunctionStatementSyntax` type.

Acceptance criteria:
- A local function like `void Bar(string s\r\n)` (closing paren on next line, one parameter) triggers SA1111.
- A local function with three parameters where the closing paren follows the last parameter on the next line triggers SA1111, and the fix moves it to the last parameter’s line.
- A local function with no parameters (e.g., `void Bar(\r\n)`) does **not** trigger SA1111.
- A local function where the closing paren is already on the same line as the last parameter does **not** trigger the diagnostic.
- No regressions in existing SA1111 behavior for methods, constructors, indexers, operators, invocations, or object creations.

Implement support for local functions in SA1111


**Title:** SA1009 does not enforce spacing around the closing parenthesis in record type inheritance

**Description**
SA1009 (`Closing parenthesis must be spaced correctly`) is not reporting spacing violations for C# 9 record declarations that specify a base type.

**Repro**
```csharp
public abstract record BaseQuery<T>;
public record MyQuery1( ) : BaseQuery<object>;      // space before )
public record MyQuery2( ) : BaseQuery<object>;      // no space after ) – actually there is a space, wrong example
```
Example cases that should trigger SA1009:
- `record MyQuery1( ) : BaseQuery<object>;` — the space before `)` should be flagged (must not be preceded by a space).
- `record MyQuery2():BaseQuery<object>;` — no space after `)` before `:` should be flagged (must be followed by a space).

**Expected behavior**
- A closing parenthesis immediately before the `:` in a record base list must follow the same spacing rules as a closing parenthesis before a `:` in a constructor initializer: no preceding space, exactly one following space.
- SA1009 should produce the corresponding diagnostic (`SA1009ClosingParenthesisMustBeSpacedCorrectly`) for these positions.
- SA1024 (`Colons must be spaced correctly`) should already handle the colon’s own spacing correctly in this context; any fix to SA1009 must not break that behavior.

**Environment**
StyleCop.Analyzers version with C# 9 record support. Language version C# 9 or later.

**Additional context**
Records with a parameter list and base type were introduced in C# 9. The analyzer previously handled constructor-initializer colons but did not recognize the base-list colon that follows the parameter list of a record declaration. This results in a gap where incorrect spacing around `)` is silently allowed.

Fix SA1009 handling of record inheritance


SA1216 (Using static directives must be placed at the correct location) does not handle file-scoped namespace declarations (C# 10). It only registers for `SyntaxKind.NamespaceDeclaration`, so using static directives inside a file-scoped namespace are silently ignored. The sibling ordering analyzers SA1209 and SA1210 already support file-scoped namespaces via `BaseNamespaceDeclarationSyntaxWrapper` from `StyleCop.Analyzers.Lightup` and register for `SyntaxKinds.BaseNamespaceDeclaration`. Update SA1216 to use the same wrapper and registration so that ordering violations are detected and fixed inside file-scoped namespaces as well.

Update SA1216 for file-scoped namespaces


**Fix SA1308 and SA1309 code fixes to strip all violating prefixes/underscores at once**

The code fix for SA1308 currently only removes the first two-character prefix (\(m\_\) or \(s\_\)), leaving subsequent prefixes intact. If a field name has multiple valid prefixes (e.g. `m_m_bar` or `m_s_bar`), the fix should strip all leading occurrences of `m_` and `s_` (case‑insensitive, in any order) until the first pair that is not a recognised prefix.

The code fix for SA1309 currently only removes one leading underscore. It should instead remove *all* leading underscores from the field name. However, if the resulting name would be empty (the field consists only of underscores), the code fix must not be offered—the same safety‑net that already existed for a single‑underscore name must apply to the fully‑underscore case.

No changes to the diagnostics themselves are needed; only the code‑fix providers should be updated to cover these multi‑prefix/underscore cases.


**Title:** SA1106 code fix does not work for `foreach` and `await foreach` empty statements

**Description**
The SA1106 diagnostic correctly flags empty statements after `foreach` loops (e.g. `foreach (var item in collection) ;`). However, the code fix that replaces the empty statement with an empty block `{ }` is **not offered** for these cases. The same issue affects `await foreach` loops on asynchronous streams.

**Expected behavior**
The code fix should be available and should replace the offending `;` with `{ }`, consistent with how the fix already works for `if`, `else`, `for`, `while`, and `do` statements. The fix must handle both synchronous `foreach` and `await foreach` loops.

**Relevant environment**
- Rules: SA1106 (readability rule about empty statements)
- C# 8 asynchronous streams (IAsyncEnumerable<T> / await foreach)


 Fix `plane_it_t` in `src/platform/linux/kmsgrab.cpp` incorrectly reporting it has reached the end when positioned on the last valid plane. Currently, after loading a valid DRM plane at index N, the iterator immediately advances its internal pointer `plane_p` to N+1. This causes the equality comparison with `end()` to return true even though the iterator holds the last valid plane, preventing the caller from processing the final entry.

Refactor the iterator to separate position advancement from plane loading. The constructor should initialize by loading the first valid plane at the current position without advancing `plane_p` past it. The `inc()` method should first increment `plane_p` to move past the current plane, then search for the next valid plane starting from that new position. This ensures `plane_p` only reaches the end sentinel when there are genuinely no more valid planes to iterate over.

Fix plane_it_t skipping the last entry when iterating


 **Issue: Retry reverting display configuration only when device topology changes**

**Current Behavior:**
When `revert_configuration_unlocked` is invoked with the `try_indefinitely` option, the scheduler repeatedly attempts to revert settings in a tight loop regardless of system state. If the reversion fails due to persistent external factors (e.g., display device locked by another process, missing hardware), this creates unnecessary log noise and CPU load with no chance of success until the hardware configuration actually changes.

**Desired Behavior:**
Change the retry logic to be reactive to hardware changes rather than time-based polling. The scheduler should maintain a snapshot of available display devices (as reported by `SettingsManagerInterface::enumAvailableDevices`) and only attempt a new reversion when the device set differs from the previous attempt.

**Specific Requirements:**
- **Initial attempt:** Execute the first revert attempt immediately (or after the existing configured delay) regardless of prior device state.
- **Subsequent attempts:** Before retrying, enumerate current devices and compare against the set from the last failed attempt. If identical, skip the revert and remain scheduled (log at debug level that we're waiting for hardware changes).
- **Topology change:** If a device is added or removed (set mismatch), attempt reversion again and log the new device list at warning level on failure.
- **Termination conditions:** Stop scheduling when `revertSettings()` returns success (`Ok`), or immediately for the `try_once` option regardless of outcome.
- **Temporary failures:** If `revertSettings()` returns `ApiTemporarilyUnavailable`, retain the current device snapshot and retry on the next scheduler invocation without treating it as a hard failure that requires device changes to retry.

**Edge Cases:**
- Ensure the `try_once` codepath remains unaffected (single attempt, then stop).
- Handle empty device lists correctly (first boot with no displays attached should still attempt reversion once).
- Maintain thread safety when accessing `SettingsManagerInterface` within the scheduler callback.

**Rationale:**
This prevents futile reversion attempts when the failure reason is static (same hardware state), while ensuring we promptly recover when users connect/disconnect displays that might resolve the blocking condition.

feat(display): retry reverting configuration only if device was added or removed (#3539)


 **Windows portable builds terminate abruptly when console window is closed**

**Problem**
On Windows, when running Sunshine as a portable application (not as a service), closing the console window via the X button or system menu causes the process to terminate immediately without executing cleanup routines. This contrasts with the graceful shutdown behavior already implemented for Unix signals (SIGTERM, SIGINT), where `lifetime::exit_sunshine` is invoked to ensure resources are released and state is persisted.

**Expected Behavior**
When the console window receives a close request, Sunshine should initiate the same graceful shutdown sequence used for other termination signals. This involves calling the existing exit handler to:
- Flush and close log files
- Terminate active streaming sessions cleanly
- Release hardware resources (GPU encoders, virtual displays)
- Persist any pending configuration changes

**Context & Impact**
This issue primarily affects portable Windows builds where Sunshine runs attached to a visible console window. Service-based installations are unaffected as they run detached from any console. Abrupt termination risks:
- Corrupted log files due to unflushed buffers
- Orphaned GPU encoder sessions requiring system reboot to clear
- Incomplete persistence of user settings changed during the session

The fix should specifically handle `CTRL_CLOSE_EVENT` while ignoring other console control signals (like `CTRL_C_EVENT` or `CTRL_BREAK_EVENT`) which may have different semantics, to avoid interfering with existing Ctrl+C handling or service control management.

Handle CTRL_CLOSE_EVENT on Windows (#2952)


 **Support 16-bit DRM formats in Vulkan DMA-BUF import for HDR content**

Currently, `vk_vram_t::import_dmabuf` in `src/platform/linux/vulkan_encode.cpp` only handles 8-bit and 10-bit DRM FourCC formats. When capturing HDR applications that output 16-bit per channel buffers (e.g., `DRM_FORMAT_ABGR16161616` or `DRM_FORMAT_ABGR16161616F`), the encoder cannot import the DMA-BUF correctly, leading to fallback errors or color channel misalignment.

**Required changes:**
Extend `drm_fourcc_to_vk_format` to recognize 16-bit FourCC codes and return both the Vulkan format and necessary component swizzling:

- `DRM_FORMAT_XBGR16161616` / `DRM_FORMAT_ABGR16161616` → `VK_FORMAT_R16G16B16A16_UNORM` (identity swizzle)
- `DRM_FORMAT_XRGB16161616` / `DRM_FORMAT_ARGB16161616` → `VK_FORMAT_R16G16B16A16_UNORM` with BGR→RGB swizzle (R↔B swap)
- `DRM_FORMAT_XBGR16161616F` / `DRM_FORMAT_ABGR16161616F` → `VK_FORMAT_R16G16B16A16_SFLOAT` (identity swizzle)
- `DRM_FORMAT_XRGB16161616F` / `DRM_FORMAT_ARGB16161616F` → `VK_FORMAT_R16G16B16A16_SFLOAT` with BGR→RGB swizzle

**Implementation notes:**
- Change the return type of `drm_fourcc_to_vk_format` to a struct containing both `VkFormat` and `VkComponentMapping`
- Apply the returned swizzle to `VkImageViewCreateInfo::components` in `import_dmabuf` to handle the BGR vs RGB ordering difference between DRM formats and Vulkan's memory layout
- Maintain existing identity swizzling for currently supported 8-bit and 10-bit formats

This ensures HDR content using high bit-depth DMA-BUFs is correctly sampled by the Vulkan compute pipeline during RGB→YUV conversion.

fix(linux/vulkan): add 16-bit DRM format support for HDR DMA-BUF import (#4962)


 **Fix VAAPI device selection on hybrid GPU laptops**

On laptops with hybrid NVIDIA/Intel graphics where the display output is wired through the NVIDIA GPU but rendering happens on the Intel integrated GPU, the KMS capture backend incorrectly attempts to use the NVIDIA card for VAAPI hardware encoding. This causes initialization failures because NVIDIA GPUs do not support VAAPI in this context, even though the display is physically connected to them.

When enumerating available displays and initializing the capture device for `mem_type_e::vaapi`, the system should detect and skip cards where `card_t::is_nvidia()` returns true, falling back to Intel or AMD devices that actually provide VAAPI support. This filtering must apply to both the display name enumeration (`kms_display_names`) and the display initialization (`kms_display`) paths.

The fix should:
- Skip NVIDIA cards specifically when the requested memory type is VAAPI, while preserving normal behavior for CUDA and system memory capture
- Include debug logging when a card is skipped due to this constraint
- Not affect non-hybrid configurations or dedicated NVIDIA encoding paths (NVENC)

fix(linux/kms): skip NVIDIA cards for VAAPI on hybrid GPU laptops (#4473)


 **Bug Report: Multi-channel gamepad events cause incorrect controller state tracking and spurious warnings**

**Problem:**
When processing controller input from multiple channels, the current implementation of `activeGamepadMask` handling assumes that mask updates arrive in order and reflect complete state transitions. This causes two issues:

1. **Out-of-order packet handling:** When `PNV_MULTI_CONTROLLER_PACKET` events arrive out of order between different controller channels, the code that XORs the old global mask against the new global mask (`updateGamepads`) incorrectly identifies which specific controllers changed state, potentially allocating or freeing the wrong gamepads.

2. **Spurious warnings on removal:** When a gamepad is removed, the system logs a "ControllerNumber not allocated" warning because the validity check relies on a global bitmask (`active_gamepad_state`) that is checked before verifying whether the specific gamepad slot is actually allocated.

**Expected Behavior:**
Each controller event should independently determine whether to allocate or free a gamepad based solely on the bit corresponding to `packet->controllerNumber` within the `activeGamepadMask` provided in that specific event. The system should not depend on ordered arrival of global mask transitions across different channels. Removing a gamepad should not produce spurious allocation warnings.

**Implementation Constraints:**
- Must handle legacy clients that do not send explicit `PSS_CONTROLLER_ARRIVAL_PACKET` but instead indicate controller arrival by setting the corresponding bit in `activeGamepadMask` within a `PNV_MULTI_CONTROLLER_PACKET`.
- Must properly manage the distinction between `controllerNumber` (the slot index from the packet) and the internal gamepad ID allocated via `gamepadMask`.
- Validity checks should verify that `controllerNumber` is within bounds of the `gamepads` vector and that `gamepads[controllerNumber].id >= 0`, rather than checking a separate global bitmask.
- The `input_t` structure should not maintain a separate `active_gamepad_state` bitmask that tracks the previous mask state for XOR comparisons.

**Impact:**
In multi-channel scenarios, gamepads may fail to initialize or may be prematurely freed when packets arrive out of order. Additionally, logs contain misleading warnings each time a controller is disconnected.

Rework active gamepad mask handling to be multi-channel-safe


 **Feature: Host-side bitrate computation for supported Moonlight clients**

Currently, the host uses the bitrate provided by the client directly for video encoding. However, this client-provided value typically represents the total network bandwidth budget rather than the actual video payload bandwidth. When FEC (Forward Error Correction) is enabled, or when audio streams occupy significant bandwidth, the video encoder ends up exceeding the intended network budget because these overheads aren't accounted for.

**Acceptance Criteria:**

When processing RTSP ANNOUNCE requests from clients that provide a configured bitrate hint:

1. **Bitrate Adjustment**: The host shall derive the actual video encoding bitrate from the client's configured value by deducting bandwidth consumed by non-video traffic, ensuring the total network utilization stays within the client's specified budget.

2. **FEC Accounting**: If FEC is enabled at moderate levels (80% or lower), the video bitrate shall be reduced to account for FEC shard overhead, ensuring total transmitted video traffic (payload + FEC) fits within the client's budget.

3. **Audio Bandwidth Reservation**: The calculation shall reserve bandwidth for the audio stream based on the negotiated audio configuration:
   - 256 Kbps per channel for high-quality mode
   - 96 Kbps per channel for standard quality mode
   - This reduction shall be capped at 20% of the total bitrate to prevent excessive reduction for high-channel-count surround streams.

4. **Protocol Overhead**: An additional 500 Kbps shall be reserved for A/V packet overhead and control channel traffic, capped at 10% of the total bitrate.

5. **Safety Constraints**:
   - If the calculated bitrate would be reduced to near-zero, the host shall allow exceeding the client's specified limit rather than producing an unusable video stream.
   - The audio bitrate assumptions used in this calculation must remain synchronized with the actual `opus_stream_config_t` bitrates defined in the audio subsystem.

6. **Backward Compatibility**: Clients not providing the configured bitrate parameter shall continue to use the existing bitrate handling path without host-side adjustment.

Compute the bitrate on the host side for supported clients


 Expand the DRM connector type name mapping in `from_view()` to recognize the full set of connector identifiers used by modern DRM drivers. The current implementation only handles a handful of common types (VGA, DVI variants, LVDS, etc.) and fails to recognize many valid connector types reported by kernels and compositors.

Add mappings for all standard connector types defined in `drm_mode.h`, including: Composite, S-Video, Component, 9PinDIN, TV, Virtual, Writeback, DPI, SPI, and USB (conditional on `DRM_MODE_CONNECTOR_USB`). Support both hyphenated and unhyphenated spellings where drivers differ (e.g., "DVI-I" vs "DVII", "HDMI-A" vs "HDMIA"), and case variations like "Virtual" vs "VIRTUAL".

Additionally, handle the "Unknown" fallback format used by some drivers where the raw numeric type is appended to the string (e.g., "Unknown123"). Parse the integer suffix and return it directly as the connector type when this pattern is detected.

Reference Mesa's `xf86drmMode.c`, Weston's DRM backend, and Mutter's monitor manager for the canonical name strings used across the ecosystem.

Improve Name->DRM_MODE_CONNECTOR mapping with new types and names


 When importing VA-API surfaces via DMA-BUF for OpenGL operations, the current implementation fails to correctly propagate hardware-specific memory layout metadata from libva to EGL. This causes import failures or visual corruption on drivers that require explicit format modifiers (common on modern Intel, AMD, and ARM Mali GPUs).

**Specific issues:**

1. **Hardcoded pixel formats**: The EGL backend assumes `DRM_FORMAT_R8` and `DRM_FORMAT_GR88` instead of using the actual fourcc codes reported by `vaExportSurfaceHandle`. This breaks when drivers report different compatible formats for the luma and chroma planes.

2. **Missing format modifiers**: The `import_target` function does not pass `EGL_DMA_BUF_PLANE*_MODIFIER_LO/HI` attributes to `eglCreateImage`, which are required for importing buffers with hardware-specific tiling or compression. Without these, EGL may fail to create valid images or interpret memory incorrectly.

3. **Incorrect VA export mode**: The export flags request `EXPORT_SURFACE_COMPOSED_LAYERS`, but the import logic treats planes as separate entities. This should use `EXPORT_SURFACE_SEPARATE_LAYERS` to ensure NV12 surfaces export with distinct layer entries for luma and chroma.

4. **Wrong plane indexing**: When extracting surface descriptors, the chroma plane incorrectly accesses `layers[0].object_index[1]` and `layers[0].pitch[1]`. With separate layers, the chroma plane resides in `layers[1]` with index `[0]` for its object, pitch, and offset.

5. **File descriptor mismatch**: The second plane descriptor references `r8.fds[0]` (luma FD) instead of `gr88.fds[0]` (chroma FD), causing the wrong DMA-BUF handle to be used for chroma data.

**Expected behavior:**
- `vaExportSurfaceHandle` should use `EXPORT_SURFACE_SEPARATE_LAYERS` to ensure each plane has independent format and modifier metadata.
- The EGL import should propagate the exact fourcc and 64-bit modifier values from `DRMPRIMESurfaceDescriptor` into `EGL_LINUX_DRM_FOURCC_EXT` and `EGL_DMA_BUF_PLANE*_MODIFIER_*` attributes.
- Plane extraction should map `layers[0]` to the first surface descriptor and `layers[1]` to the second, with correct FD references per descriptor.

**Impact:**
Hardware-accelerated video encoding fails or produces corrupted output on systems requiring format modifiers, or when the VA driver doesn't use the assumed hardcoded pixel format constants.

Correctly passthrough plane format and modifiers when importing VA surface


 **Title:** UTF-8 encoding not enforced for log output and C/C++ standard library functions on Windows

**Body:**
Sunshine's log output and text handling operations do not consistently use UTF-8 encoding on Windows. When log messages contain non-ASCII characters—such as international characters in file paths, user names, or media titles—the output is corrupted or uses the system's default ANSI codepage rather than UTF-8. This occurs because neither the C standard library locale nor the C++ global locale is explicitly configured to use UTF-8 encoding, causing `boost::log` and other stream operations to fall back to the system's default codepage.

This affects:
- **Log readability:** The Boost.Log framework writes messages using the global C++ locale, which on Windows defaults to the system's ANSI codepage. Viewing logs in UTF-8-capable editors results in mojibake for any non-ASCII content.
- **Cross-platform consistency:** File paths and strings with Unicode characters are handled correctly on Unix-like systems (which default to UTF-8) but may be corrupted on Windows.
- **International user support:** Users with accented characters, CJK text, or emoji in their environment see garbled output in logs and potentially incorrect behavior in path handling.

**Expected Behavior:**
- All log output must be written in UTF-8 encoding, ensuring proper display of international characters across all platforms
- The application must configure the C library locale to UTF-8 on Windows (available since Windows 10 version 1803) to ensure standard library functions use correct encoding for file operations and text processing
- The global C++ locale must be configured with a UTF-8 codecvt facet to ensure Boost.Log and other stream operations perform correct character conversions between `wchar_t` and `char` representations

**Impact:**
Without UTF-8 enforcement, log files are unreadable for international users, and operations involving Unicode file paths or strings may behave unpredictably on Windows systems, complicating debugging and potentially causing file access failures.

Use UTF-8 conversion for log output and C standard library functions


 **Issue:** When the UAC secure desktop appears (or other desktop switches occur), the display capture system may unexpectedly switch to a different monitor because the display enumeration becomes inconsistent.

**Expected Behavior:**
- Display enumeration should provide a consistent view of available outputs even if the desktop changes during the enumeration process.
- If the current desktop becomes uncapturable (e.g., UAC secure desktop), the enumeration should fail predictably rather than reporting partial/inconsistent results that cause unexpected display switching.
- Normal display initialization (outside of enumeration) should continue to work reliably.

**Current Problem:**
During display enumeration, the system tests each adapter/output combination via `test_dxgi_duplication` to determine if it can be captured. Currently, `syncThreadDesktop` is called individually within each test iteration. If the desktop switches to the secure desktop mid-enumeration (between testing different GPUs), some outputs may report as available while others return `E_ACCESSDENIED`, leading the capture code to select a different display than originally intended or switch displays unexpectedly.

**Acceptance Criteria:**
1. Display enumeration must be atomic with respect to desktop state—the system should synchronize the thread desktop once at the beginning of the enumeration pass and maintain that consistent view for all adapter/output tests.
2. When testing capture capability during enumeration (as opposed to display initialization), if the system receives `E_ACCESSDENIED`, it should fail fast rather than retrying with delays, since the desktop state is fixed for the duration of the enumeration pass.
3. Display initialization (`display_base_t::init`) should continue to resynchronize the thread desktop as needed before attempting duplication, since it operates on a specific target device rather than enumerating multiple devices.
4. The implementation must ensure that enumeration results are consistent—all outputs should reflect the same desktop state, preventing scenarios where the capture switches from the intended display to another simply because the secure desktop appeared during enumeration.

**Edge Cases:**
- UAC prompt appearing between testing outputs on different GPUs
- Fast user switching or lock screen activation during enumeration
- Multiple GPUs where desktop access permissions differ based on timing of the secure desktop transition

Avoid display switching unexpectedly when the UAC secure desktop appears


 **Case-insensitive username matching for web UI authentication**

When authenticating to the Sunshine web UI, the username comparison currently requires an exact case match (e.g., "Admin" is rejected if the configured username is "admin"). This creates friction for users who may capitalize their username differently during login than how it was originally configured.

**Acceptance Criteria**

- Username comparisons during authentication must be case-insensitive, so that "User", "user", and "USER" are all treated as equivalent to the configured username.
- Password validation must remain strictly case-sensitive.
- Empty username handling must preserve existing behavior: if no username is configured, authentication should proceed based solely on password validation (if configured) or allow unrestricted access (if no credentials are set).
- The change must apply to both the initial login authentication and the password change flow (where the existing username is verified before allowing a password update).

**Constraints**

- Do not modify the storage format of the username in the configuration; normalization should only occur during comparison operations.
- Do not alter the password hashing logic or the salt generation mechanism.
- Ensure the comparison works correctly for Unicode usernames (locale-aware case folding is acceptable, but basic ASCII case-insensitivity is the minimum requirement).

fix(ui): allow case-insensitive username (#1249)


 **RTSP: Sanitize clientRefreshRateX100 when it diverges from maxFPS**

Moonlight Android (and potentially other clients) incorrectly populate the `x-nv-video[0].clientRefreshRateX100` field with the display's physical refresh rate rather than the requested streaming framerate. This causes frame pacing mismatches because the server trusts the value for timing calculations even when it diverges significantly from `x-nv-video[0].maxFPS`.

We should validate the client-provided refresh rate when parsing the ANNOUNCE request in `src/rtsp.cpp`. If `clientRefreshRateX100` is provided, derive the FPS (`value/100`) and compare it to `maxFPS`. If the ratio is outside the range [0.99, 1.01], discard the value by treating it as unset (0). This ensures the streaming pipeline falls back to standard framerate logic rather than using an unrelated display refresh rate.

**Safety constraints:**
- Only perform the validation when `clientRefreshRateX100 > 0` to avoid division issues
- The 1% tolerance accommodates minor floating-point variance (e.g., 59.94Hz vs 60Hz) while still catching cases where the client sent 120Hz or 144Hz for a 60Hz stream request

**Observable behavior:** After this change, sessions where the client sends a mismatched refresh rate should no longer exhibit stuttering or timing artifacts caused by the mismatch; instead, they should use the framerate derived from `maxFPS`.

fix(rtsp): ignore clientRefreshRateX100 if more than 1% variance from framerate (#5027)


 **Feature Request: Extend NVENC intra-refresh support to H.264 and add recovery point SEI**

**Background**
Currently, the standalone NVENC encoder supports intra-refresh mode for HEVC when the client requests it via `enableIntraRefresh`, but H.264 encoding ignores this flag. Additionally, when intra-refresh is enabled, we should be emitting recovery point SEI messages to help decoders identify random access points correctly.

**Requested Behavior**
1. When `client_config.enableIntraRefresh` is set and the codec is H.264, configure the encoder with intra-refresh parameters (period=300, count=299) if the hardware reports `NV_ENC_CAPS_SUPPORT_INTRA_REFRESH` capability.
2. For both H.264 and HEVC intra-refresh configurations, set `outputRecoveryPointSEI = 1` in the codec configuration.
3. If `NV_ENC_CAPS_SINGLE_SLICE_INTRA_REFRESH` is available, enable single-slice intra-refresh for better compatibility.

**Safety Constraints**
- **Capability Check**: Always verify `NV_ENC_CAPS_SUPPORT_INTRA_REFRESH` before enabling; log an error if the client requested intra-refresh but the encoder doesn't support it.
- **Single-slice fallback**: If single-slice intra-refresh is not supported, log a warning but continue with multi-slice intra-refresh rather than failing initialization.
- **Period/count alignment**: Maintain the existing HEVC values (period=300, cnt=299) for consistency across codecs.

**Files Involved**
- `src/nvenc/nvenc_base.cpp`: Encoder initialization logic in `create_encoder()` where codec-specific format configurations are set up.

**Acceptance Criteria**
- H.264 streams honor the intra-refresh flag when hardware supports it.
- Both H.264 and HEVC intra-refresh streams include recovery point SEI NAL units.
- Appropriate warnings are emitted when hardware capabilities are partially missing.

feat(nvenc): support intraRefresh for h264, add outputRecoveryPointSEI=1 to intra-refresh config for h264 and hevc (#5091)


 Windows hosts running Sunshine are not appearing in the service discovery list on Apple devices (iOS, macOS, iPadOS, tvOS). Network analysis reveals that the Windows DNS-SD implementation generates malformed mDNS TXT records containing zero strings when no TXT metadata is explicitly provided, which violates RFC 1035 section 3.3.14 (a TXT record must contain one or more \<character-string\>s). Apple's mDNS resolver enforces strict compliance with this specification and rejects the entire service advertisement when encountering zero-length TXT records, causing discovery to fail.

Acceptance criteria:
- Windows hosts must be discoverable by Apple devices via standard mDNS/Bonjour service discovery
- The mDNS service advertisement must generate RFC 1035-compliant TXT records
- When advertising a service instance without TXT key-value pairs, the TXT record must encode exactly one empty string rather than zero strings to satisfy RFC 1035 requirements

Fix discovery of Windows hosts by Apple devices


 When using KMS display capture on Linux, specifying a monitor index that does not exist causes a crash (segmentation fault or null pointer dereference) instead of gracefully reporting an initialization error. For example, requesting monitor index 2 on a graphics card that only has a single connected display triggers the crash during `display_t::init`.

The expected behavior is that the initialization should detect when the requested `monitor_index` cannot be matched to any available connector/CRTC, log an error message indicating which index was not found, and return -1 to signal failure. This allows the caller to handle the missing display appropriately rather than crashing.

The issue appears to occur because validation that the monitor was successfully located happens after a control flow path used when the monitor *is* found. If the nested iteration over the card's resources completes without finding the specified monitor, the code proceeds to set up cursor plane capture using invalid/uninitialized CRTC IDs, leading to the crash.

**Requirements:**
- If the specified `monitor_index` is not found after iterating through all available connectors and CRTCs, the function must immediately log an error and return -1.
- This validation must occur before any subsequent logic (such as cursor plane lookup) attempts to use the monitor's resources.
- The fix applies to `kms::display_t::init` in `src/platform/linux/kmsgrab.cpp`.

Fix crash on monitor index not found in KMS


 **Bug Report: Async logging sink dies on I/O errors, causing silent log loss**

**Observed behavior:**
When the logging backend encounters an I/O error (e.g., disk full, log file locked by external process, broken stdout pipe), the async sink's background thread terminates abruptly. Once this occurs, *all subsequent log records are silently discarded* with no indication to the user or calling code that logging has failed. This makes diagnosing production issues nearly impossible when the very mechanism for capturing diagnostic information stops working without warning.

**Expected behavior:**
The logging system should be resilient to transient backend failures. If a single write operation fails, the background logging thread should remain alive and continue processing subsequent log records. The failure of one log write should not cause total cessation of logging for the lifetime of the process. At minimum, the system should protect the sink from dying so that diagnostic information isn't lost during critical error conditions.

**Impact:**
- Silent data loss: critical error messages disappear without a trace when most needed
- Debugging impossibility: when investigating disk/permission issues, the logs that would explain the root cause are missing because the logger died from the same issue
- Production reliability: long-running services (e.g., Windows services) lose observability after the first I/O hiccup

**Affected components:**
- `text_sink` (asynchronous wrapper around `text_ostream_backend`)
- `init()` function in `src/logging.cpp` where the sink is configured

**Constraints:**
- The fix must not introduce deadlock risks in the async sink's threading model
- Must maintain thread-safety guarantees of the existing logging infrastructure
- Should not significantly degrade logging performance or introduce blocking operations in the exception path

fix(log): prevent sink destruction caused by backend exceptions (#4694)


 **Ticket:** Optimize IV buffer allocation in streaming encryption hot paths

**Description:**
The real-time streaming pipelines (audio, video, and control) currently allocate fresh `crypto::aes_t` buffers for the initialization vector (IV) on every packet encryption operation. This occurs in:
- `encode_audio()` for audio packets
- Video packet encryption in `videoBroadcastThread()`
- Control message encryption/decoding in `encode_control()` and the control receive path

This creates unnecessary heap allocation pressure in latency-sensitive streaming paths.

**Goals:**
1. Eliminate per-packet IV allocations by reusing buffers across the stream lifetime
2. Maintain correct IV construction per packet (sequence number-based IVs for audio/control, counter-based for video with stream type identifiers)
3. Preserve thread safety across concurrent audio, video, and control threads

**Constraints:**
- IV content must remain unique per packet per cryptographic requirements. Audio uses CBC mode with 16-byte IVs derived from `avRiKeyId` and sequence number. Video uses 12-byte IVs incorporating `gcm_iv_counter` and stream type tag ('V'). Control uses deterministic IV construction per NIST SP 800-38D Section 8.2.1 with sequence numbers and direction tags ('CH', 'CC').
- Thread isolation: audio, video, and control run on separate threads and require independent IV storage (session sub-structs should hold reusable buffers).
- The `encode_audio()` function currently constructs the IV internally; this logic must be preserved but should populate a pre-allocated buffer passed by reference rather than returning a new allocation.
- Must handle both `SS_ENC_CONTROL_V2` and legacy encryption modes for control stream.

Avoid unnecessary reallocation of the IV for every packet


We need to ensure that the threat model editing pane templates are selected correctly based on the model type (CIA, LINDDUN, STRIDE) in all places where threats are generated or edited.

Currently, the template selection logic in the diagram controller (when generating threats) and in the element threats directive (when editing threats) handles `CIA` and `LINDDUN` explicitly but has no dedicated branch for `STRIDE`. Models that use `STRIDE` rely on a generic fallback, which works but is not explicit. For backward compatibility with older models that have no type at all (`null`), the fallback to the STRIDE pane should be preserved; any truly unrecognised type should also fall back to STRIDE.

The request:

- Add an explicit `STRIDE` case to the template selection in both the diagram controller's `selectTemplate` function and the element threats directive's `getTemplate` function, so that the Stride edit pane is selected directly instead of only through a default path.
- Keep the existing behaviour: `null` (no type) selects the STRIDE pane, and any unrecognized type also defaults to the STRIDE pane.
- Extend the unit tests to cover each known model type (`CIA`, `LINDDUN`, `STRIDE`), as well as an unrecognised type and the `null` case, for both the diagram controller threat generation flow and the element threats directive edit flow. The tests should verify that the correct edit pane template is used for each type.


We need a threat model version migration mechanism in the front-end application that upgrades legacy v1 threat model data to the current v2 format. The conversion should be invoked whenever the application loads a model, so that users with older models get seamlessly updated.

**Acceptance criteria:**

*Version detection*
- If a threat model object has no `version` field, assume it is version `"1"`.
- If the version is a known string (listed in a set of supported model versions), use it.
- If the version is any unrecognised string, throw an error clearly indicating the invalid version.

*v1 → v2 migration*
When a model is at version `"1"`, upgrade it to `"2.0"` by transforming it as follows:
- Set `version` to `"2.0"`.
- Flatten `summary.title` → `title`, defaulting to an empty string if absent.
- Flatten `summary.owner` → `owner`, defaulting to empty string.
- Copy top-level `reviewer` to `reviewer`, defaulting to empty string.
- Flatten `summary.description` → `description`, defaulting to empty string.
- Extract contributor names from the array `detail.contributors` (objects with a `name` property) into a flat `contributors` array of strings. If the contributors array is missing, default to an empty array.
- Keep the `diagrams` array from `detail.diagrams` as `diagrams`, unchanged (do not attempt to flatten its contents).

*No-op for current version*
If the model is already at the latest version (currently `"2.0"`), return it unmodified.

*Extensibility*
The upgrader should be structured so that adding a future migration step (e.g. v2 to v3) is straightforward: a developer should only need to add a new entry to a list of upgraders, providing a dedicated upgrade function that transforms from the previous version to the new one, without rewriting the core orchestration logic.

*Stable version constants*
The set of known model version strings (`"1"`, `"2.0"`, etc.) must be defined in one place, be immutable, and be used consistently by all parts of the migration code. New version additions should only require updating that single definition plus providing the new upgrader.

The new service should be placed under the Vue front-end at `td.vue/src/service/migration/` so it can be easily discovered and extended.

Adding migration scripts to the front-end, set up in a way that future migrations can be made a bit easier if necessary


**Title:** TM-BOM import does not preserve `assumptions` – include them in the summary description

### Context
OWASP Threat Dragon supports importing threat models in the TM-BOM format. The imported model is converted into the internal Threat Dragon model, which includes a `summary` with a `description` field.

### Problem
TM-BOM models can contain an `assumptions` array (each entry has `validity`, `description`, and optionally `topics`).  Currently, when importing such a model, the assumptions are completely discarded.  Users who rely on assumptions to capture important context or constraints lose that information after import.

### Desired behaviour
When importing a TM-BOM model that contains assumptions, the assumptions should be preserved in the imported Threat Dragon model.  They should be appended to the existing `summary.description`.  The rendered text must:

- Number the assumptions sequentially, starting from 1.
- Show the `validity` string for each assumption.
- Show the `description` text for each assumption.
- If `topics` is present and non-empty, show a comma-separated list of the topics (e.g. `topics covered: topic1, topic2`).
- Separate the assumptions block from any pre-existing description with clear whitespace (e.g. two newlines) so the original description and the assumptions are visually distinct.

This addition does not need to be round-tripped back to TM-BOM format (export can remain unchanged).  The sole requirement is that imported models no longer throw away this information.


```
Extend the TM-BOM import so that assumptions with topic links are merged into component descriptions, not just listed in the summary.

Specifically:
- For each assumption that has a `topics` array, append the assumption to the `description` of every component whose `id` matches a topic entry. The appended text should follow the format: a newline + the `validity` field + ` assumption : ` + the `description` field + a newline.
- Assumptions without a `topics` field (or with an empty/null topics) should continue to appear only in the threat model summary description.
- In the summary description, stop adding the "topics covered: ..." line for assumptions, since topic-linked assumptions are now placed directly on components.

This should mirror how data sets (`sets`) are already merged into component descriptions during import.
```


**Title:** TM-BOM import is missing diagram nodes (actors, components, data stores)

**Background**
The TM-BOM → Threat Dragon migration (`td.vue/src/service/migration/tmBom/`) currently only creates trust‑boundary boxes and places them in diagrams. Actors, components, and data stores from the TM-BOM model are completely ignored. The result is that a user sees empty trust zones with no elements inside them.

**Required behaviour**
When importing a TM-BOM model, the migration must also create diagram cells for:

- `actors` → `tm.Actor` nodes
- `components` → `tm.Process` nodes
- `data_stores` → `tm.Store` nodes

**Trust‑zone placement**
- If a node has a `trust_zone` value matching a trust‑boundary box’s `symbolic_name`, that node should be placed **inside** that boundary box.
- Nodes with no `trust_zone` (or `null`) should be placed **outside** the boundary boxes. When trust zones exist, these untrusted nodes should be arranged around the edges; when there are no trust zones, they should go into a single default boundary‑box area that covers the whole diagram.
- The boundary boxes themselves must still appear in the diagram, layered behind the nodes that belong to them.

**Entity construction**
To build a diagram cell we need the full default entity object (including `shape`, `size`, `attrs`, `position`, etc.), not just the `data` sub‑object. Today `@/service/entity/default-properties.js` only exports `defaultData`. We need a way to obtain a deep‑copied complete entity template for a given type so that the migration can populate it with the real name, description, and id from the TM-BOM model.

**Boundary‑box sizing**
The current boundary‑box code uses hardcoded `nodeSize` and `padding` constants. Because the nodes we place inside must influence the box dimensions, those values should be provided by the caller instead of being baked in. This ensures the boxes are sized consistently with the actual nodes they contain.

**Edge cases / invariants**
- **No trust zones in the model:** all nodes go into one default trust‑boundary‑box area (the same shape as today’s fallback).
- **No explicit diagram defined:** the generated default diagram must include the imported nodes as well, not just the boundary boxes.
- The existing tests for boundary‑box reading need to be adjusted to pass the now‑externalised `nodeSize`/`padding` parameters; the descriptions in the test fixture for the experimental zone are currently misleading and should be corrected (they claim “production”).
- The new behaviour must use the i18n‑aware shape names that the entity defaults are being updated to (e.g. `tc('threatmodel.shapes.actor')`) so that all cell labels are properly localised.

add handling for diagramnodes from TM-BOM


**Title:** Newly added diagram components are not selected; flows/trust boundaries should not be auto-selected

**Observed behavior**
When a component (actor, process, store, text, trust boundary box) is added to the diagram from the stencil, the element appears on the canvas but is **not** visually selected. The previously selected element, if any, remains selected.

For flow and trust-boundary-curve stencils, after the edge is created, it is also not selected (which is actually acceptable), but the selection state can be inconsistent with what the user expects.

**Expected behavior**
1. Adding any “node” component (actor, process, store, text, trust boundary box) should cause the canvas to **clear the current selection** and then **select the newly added element**, giving immediate visual feedback and making it ready for editing without an extra click.
2. Adding a flow or trust-boundary-curve edge should **not** select the edge automatically. Selecting an edge right after creation is surprising and less useful, because the user typically wants to adjust endpoints or continue connecting, not to interact with the edge’s properties.
3. The selection behavior should be consistent across all node types.

**Impact**
Users must manually click each new element to select it, adding friction to the modeling workflow. The omission also leaves a stale selection from a previous element, causing confusion when the property panel shows old data.

**Additional context**
- The fix should apply after any necessary stencil-to-edge conversion (e.g., when a flow-stencil is dropped, it gets converted to a real edge before selection logic runs).
- A similar problem was visible in the code (a commented-out `resetSelection` call) suggesting this was intended but not implemented.


**Title:** Implement token-based logout endpoint to replace stub

**Report:**
The current logout flow is broken. The `POST /logout` endpoint returns a 400 error with `"Not implemented on the server"`, which blocks the frontend from logging users out. The old session-based form logout (`GET /logoutform`) is also no longer relevant for our token-based authentication.

We need a working logout endpoint that properly revokes a refresh token.

**Expected behavior:**
1. A new `POST /api/logout` endpoint, **protected by bearer token authentication** (i.e., part of the authenticated API routes, not the public unauthRoutes).
2. The endpoint accepts a JSON body with a `refreshToken` field.
3. It removes that token from the token repository (`tokenRepo.remove`).
4. Handling edge cases:
   - If the refresh token is **missing**, log a warning (`req.log.warn({ security: true }, ...)`) and still return a successful response (HTTP 200, empty body).
   - If the token removal throws an **error**, catch it, log an error (`req.log.error({ security: true }, ...)`), and still return 200.
5. Use the standard `responseWrapper.sendResponse` to wrap the handler function.
6. Clean up the **obsolete `/logoutform` and `/logout` routes** and remove their handler functions (`logoutform`, `logout`) from the home controller. The home controller should only retain the `index` function.
7. Adjust any test mocks so they no longer provide the now-unnecessary `logOut` method.

This will align logout with the rest of the token-based API and fix the frontend logout capability.


**Title:** Unify style updates for all threat model cell shapes (nodes and edges)

**Background**
Currently, when threat model data changes (threat status, encryption flags, trust boundary markings, etc.), the visual styling of edges (flows) and nodes is handled through different code paths. Edges use a direct attribute-manipulation helper, while nodes use a shape-specific `updateStyle` method. This causes inconsistency and makes future styling enhancements harder. Additionally, style updates are being triggered on cell unselection, which is unnecessary and can cause flickering / redundant work.

**Acceptance criteria**

1. **Common style update path**
   All cell shapes – both nodes and edges – must receive their visual styles through a single interface: a method named `updateStyle` that is called with the computed style values (color, dash pattern, stroke width, source marker). The flow shape (the edge connector) must implement `updateStyle` so that it correctly sets the line’s stroke, dash, stroke width, and source/target arrow markers.

2. **Default flow marker**
   The flow shape’s default appearance must include a block-style target arrow marker (it should not be hidden by default).

3. **Style updates only on data change**
   Style updates must be triggered **exclusively** by the `change:data` event. They must not fire during cell selection or deselection. Remove any explicit calls to the style-update and name-update logic from the cell-unselection handler.

4. **Clean up unused constants**
   Remove any orphaned style constant definitions (e.g., the `unencrypted` object hardcoding a path data) that are no longer referenced anywhere in the style updating logic.

5. **Import hygiene**
   Update the threat service import in the style-update module to use the project’s standard `@/service/` module alias instead of a relative path, keeping it consistent with the rest of the codebase.

**Non-goals**
- No changes to the actual styling rules (colors, dash patterns, trust-boundary overrides) – only the mechanism that applies them should change.
- No changes to how the threat modeling data is computed or stored.

refactor component update of style attributes


**Title:** Restore context option support for `StyledElements.Notebook.goToTab`

**Description:**
The `goToTab` method of the `StyledElements.Notebook` class used to support an optional second argument that allowed passing a `context` value to be relayed to the `change` and `changed` event listeners. That support was inadvertently removed. This behaviour needs to be restored so that client code can attach metadata to tab switches.

**Expected contract:**
- Signature: `goToTab(tab[, options])`
- `options` is an optional plain object that may contain a `context` property (any value).
- When `options` and `options.context` are provided, the `change` and `changed` events must fire with that context value as an extra third argument to the event callbacks (i.e., `listener(notebook, oldTab, newTab, context)`).
- When `options` is omitted or `context` is not specified, the events should fire with exactly the same parameters as today (only `oldTab` and `newTab`), preserving full backward compatibility for all existing callers that call `goToTab(tab)`.
- The JSDoc comment above the method should document the new parameter and its `context` field.

**Acceptance criteria:**
- Calling `goToTab(tab, { context: someValue })` causes `change`/`changed` listeners to receive `someValue` as the fourth argument.
- Calling `goToTab(tab)` without a second argument does not change existing event payloads.
- The method still behaves correctly for all existing tab-switching logic (visibility, focus, early return when the tab is already visible).

Readd support for the context option when using the StyledElements.Notebook.goToTab method


```
The workspace merge service currently has two separate endpoints: one for merging from a local workspace, and one for merging from a mashup template. This is causing issues and the frontend code is not aligned. Unify the merge logic into a single endpoint. The new endpoint should accept a POST request with JSON body containing either:
- `"mashup": "vendor/name/version"` to merge a mashup from the catalogue
- `"workspace": <workspace_id>` to merge content from another workspace

Update the frontend JavaScript code that triggers merge operations (both in the workspace context menu and in the mashup catalogue) to use this unified endpoint with the correct method and payload. The endpoint should return HTTP 204 No Content on successful merge, and the frontend should not attempt to parse a response body on success. Also fix the `WorkspaceVariableCollection` POST endpoint to return 204 instead of 200, as it does not return a body.
```


**Title:** Uninstalling a marketplace pack offering does not remove all resources

**Description:**
A user can install a “pack” offering from the FIWARE marketplace, which delivers multiple widgets/operators into the local catalogue. After installation, the offering’s main button becomes “Installed”, but clicking it provides no way to uninstall – or, if an uninstall action is available, it only removes the first resource in the pack, leaving the others still installed.

**Expected behaviour:**
- When all installable (MAC) resources from a pack are present in the local catalogue, the offering’s main button should be labelled **Uninstall**. Clicking it must attempt to uninstall *every* installable resource belonging to that offering, not just the first one.
- Resources that are not installable MAC components (e.g. non‑widget/operator assets) should be skipped without error.
- After successful uninstallation of all installable resources, none of the pack’s components remain in the local catalogue, and the offering’s button reverts to **Install** so the user can reinstall the pack later.
- The UI should remain consistent across both the offering list view and the detailed resource list inside an offering, correctly reflecting the installed state and allowing uninstall at individual resource level as well.

**Impact:**
Users cannot cleanly remove a pack offering, leaving stale components in their workspace and causing frustration. The fix should make pack uninstall work as a complete operation, matching the install experience.


We need to refactor `StyledElements.CommandQueue` so it can handle command callbacks that return Promises, while still supporting its legacy step‑based execution model. This is a small but important change to enable asynchronous command processing in the WireCloud dashboards.

**Current behavior (the problem)**
Right now `CommandQueue` only works with an `initFunc` that returns an array of step times for a step‑based workflow. There is no way to schedule commands that involve inherently async work (e.g., network requests, animations) because the queue has no concept of waiting for a Promise.

**Desired behavior (acceptance criteria)**

1. **Constructor signature**
   The queue should accept `(context, callback, stepFunc)`. The old `initFunc` is replaced by `callback`.
   - `context` – the `this` value for callbacks.
   - `callback` – **required**, called with `(context, command)` every time a command is picked from the queue.
   - `stepFunc` – **optional**, only used when the callback returns an array of step times (legacy compatibility).

2. **Sequential processing via `addCommand(command)`**
   - When a command is added, the queue starts processing if it is idle.
   - Undefined / null commands are silently ignored – they must **not** start the queue or change the `running` state.
   - If a command is ignored while the queue is already running, nothing happens.

3. **Callback return handling**
   The `callback` can return three kinds of values:
   - **Falsy** (e.g., `undefined`, `null`, `false`): the command is skipped and the next one starts immediately.
   - **Array of numbers** (legacy path): treated as step times. The queue enters step‑based execution using `stepFunc(withStepIndex, context)`. Each step waits the corresponding delay from the array. When `stepFunc` returns `false`, the command is considered finished and the next one begins.
   - **Promise**: the queue waits for the Promise to settle (fulfilled or rejected) and then starts the next command. During this time no other commands are processed.

4. **FIFO ordering and mutual exclusion**
   Exactly one command is “in‑flight” at any time. The next command is never started until the current one finishes (Promise settled, step sequence complete, or immediately when the callback returns falsy).

5. **Running state**
   Expose a public **read‑only** property `running`. It must be `true` while a command is being processed (including while waiting for a returned Promise or during a step sequence) and `false` only when the queue is idle.

**Non-goals / backward compatibility**
- The old step‑based path (timer steps) must still work when `stepFunc` is supplied and the callback returns an array.
- Internal implementation details (e.g., how state is stored) are not prescribed, but the external API described above must be honoured.

**Where to look**
The existing class lives in `src/wirecloud/commons/static/js/StyledElements/CommandQueue.js`. The Grunt test configuration may need minor adjustments to support any new polyfills (if needed for test environment), but the primary change is in the class itself.


## Request: Improve abstraction for resource-detail interactions in Selenium tests

### Background
Our Selenium test suites for resource management (catalogue, fiware marketplace, local catalogue, markets) contain a lot of duplicated, low-level logic for opening and interacting with resource detail panels. A typical test:

1. Searches for a resource by name via `search_in_results`
2. Scrolls to the returned DOM element and clicks it
3. Waits for the `.details_interface` to become enabled
4. Directly queries the DOM for version selects, advanced-operation buttons (e.g. “Delete”, “Publish”, “Uninstall all versions”), documentation links, etc.
5. Manually clicks the back button to close the detail view, often wrapped in `try/finally`

This makes tests verbose, brittle, and hard to maintain. Stale element exceptions, timing issues, and forgotten clean-up (leaving the detail view open) are recurring problems.

### Desired behavior
We need a higher-level test helper that automatically manages the lifecycle of a resource detail view, so that tests can express actions like “open detail, get version list, switch version, click an advanced operation” without touching the underlying Selenium finders and waits directly.

### Requirements
- **Lifecycle safety**: Provide a context-manager pattern (or equivalent) so that opening and closing the detail view is automatic and happens even if an assertion fails.
- **Return from `search_in_results`**: `MarketplaceViewTester.search_in_results` (and its override in `MyResourcesViewTester`) should return an object that wraps both the list-entry element and the open detail panel, instead of a bare `WebElement`.
- **Version interaction**: The returned helper should expose:
  - a method to obtain the list of available versions,
  - a method to switch to a specific version (and wait until the detail interface is ready again).
- **Advanced operations**: The helper should allow clicking an “advanced operation” button by its text label (e.g. “Delete”, “Delete all versions”, “Uninstall”, “Publish”, …). If the requested operation is not present, it should raise an appropriate exception (e.g. the equivalent of Selenium’s `NoSuchElementException`), so tests can test that an operation is *not* available.
- **No manual back-navigation**: Tests should no longer need to locate and click the back button explicitly. The helper must detect whether the detail view is currently active and, if so, close it during cleanup.
- **Frontend support**: The `OfferingDetailsView` (FiWare marketplace) currently lacks a way for the test helper to know it is the active subview. It should expose a discoverable identifier (e.g. a `view_name` property) so the helper can reliably decide whether it needs to close the detail view. Additionally, the detail view should carry the CSS class `details_interface loading` when rendered, to align with existing wait conditions.
- **Existing tests**: All the existing Selenium tests in `wirecloud/fiware/tests/selenium.py`, `wirecloud/platform/localcatalogue/tests.py`, and `wirecloud/platform/markets/tests.py` that perform detail-view interactions must be updated to use the new abstraction. Their high-level intent (what is tested) must remain unchanged, but they should no longer contain manual DOM queries, waits, or back-button clicks for basic detail interactions.

### Out of scope
- Changing the behaviour of the UI itself (except for the testability improvements described above).
- Adding new test scenarios; this is purely a refactoring + helper introduction.

### Acceptance hints
- Tests are easier to read and less likely to break when DOM structure changes.
- A test that fails while a detail panel is open will not leave the panel open for subsequent tests.
- The `fiware`, `localcatalogue`, and `markets` Selenium tests pass reliably after the change.


```
Title: TargetEndpoint.connect throws TypeError when passed a valid SourceEndpoint

In the wiring module, `TargetEndpoint.prototype.connect(input, connection)` currently
rejects a `SourceEndpoint` as input. The type guard is checking the wrong constructor,
so a correct call like

    targetEndpoint.connect(sourceEndpoint, someConnection)

throws a `TypeError` instead of establishing the connection.

Compare with the sibling method `SourceEndpoint.prototype.connect`, which correctly
accepts a `TargetEndpoint`. The guard in `TargetEndpoint.connect` should similarly
validate that the input is a `SourceEndpoint`.

Expected behaviour after the fix:

- `targetEndpoint.connect(sourceEndpoint, conn)` succeeds and registers the connection.
- Passing anything that is not a `SourceEndpoint` (including `null`) continues to throw
  a `TypeError`.
- The existing duplicate-connection guard (connecting the same source endpoint twice
  is a no‑op) must remain intact.
```


**Title:** Public workspace toolbar shows enabled editing buttons and missing restrictions for non‑owners

**Description**
When a logged‑in user opens a public workspace they do **not** own, the workspace toolbar incorrectly presents the “add widget” (plus icon) and “wiring” (puzzle piece) buttons as enabled. Since the user cannot modify the workspace, these buttons should be disabled. Additionally, the workspace allows adding new tabs (via the notebook), which should also be prohibited for a non‑editable workspace.

For anonymous (not logged‑in) users, the toolbar should be completely empty, but currently several buttons (add widget, wiring, my resources, marketplace, and merge) are still displayed.

**Acceptance criteria**

1. **Logged‑in user visiting a public workspace they do not own**
   - The “add widget” (wallet/plus) and “wiring” (puzzle piece) buttons are **visible but disabled** (greyed out / non‑interactive).
   - The “my resources” and “marketplace” buttons remain **enabled**.
   - The “merge” button is **not present** on the workspace toolbar (it should be removed entirely).
   - Adding a new tab (clicking the “+” on the tab bar) is **prevented**; the action does nothing.

2. **Anonymous user (not logged in)**
   - The workspace toolbar is **empty** (no buttons are rendered at all).

3. **Permission model**
   - The ability to add widgets, use wiring, and add tabs is governed by a single “edit” permission on the workspace.
   - A workspace that cannot be removed by the current user is **not editable**; any UI elements that would allow modification are disabled or hidden, and programmatic access to those actions is blocked.

**Current behaviour**
- Editing buttons appear active for non‑owners.
- Tabs can be added on public workspaces by non‑owning logged‑in users.
- Anonymous users see a full set of toolbar buttons (including a merge button), none of which should be available in a read‑only view.

**Note**
The exact set of buttons that should appear for a logged‑in user who owns the workspace (editable case) is outside the scope of this ticket – the bug only concerns read‑only workspaces (public, not owned) and anonymous views.


## Bug Report: Minimized Widgets Report Incorrect Sizes and Break Layout Positioning

**Title:** Minimizing a widget causes incorrect size reporting in the mashup context and fails to shift widgets positioned below it

**Severity:** Medium – data corruption in widget context API and visual layout breakage

**Affected area:** Widget minimize/maximize behavior, particularly in column and similar stacked layouts

**Observed behavior:**

1. **Context size reporting is wrong for minimized widgets.** When a widget is minimized, the `height` field reported through the widget context API becomes `0` instead of reflecting the actual height the widget occupies in the layout (the minimized header height). The `heightInPixels` field is correctly `0`, but `height` (in layout units) should still be the minimized widget's collapsed height so other widgets and mashup logic can rely on it for layout calculations.

2. **Widgets below a minimized widget do not shift position.** In a column layout, when widget A is minimized, widget B positioned directly below it stays at its original screen position instead of moving up to sit just below the collapsed widget A. Maximizing A later also fails to push B back down if B had not been manually moved.

3. **Maximize does not restore the full original height from the widget's own perspective.** After minimize→maximize, the widget's own reported height and pixel dimensions are not guaranteed to match the pre-minimize values.

**Expected behavior:**

- When a widget is minimized, the context API must report `height` as the layout-unit height of the minimized widget (the collapsed header bar), not `0`. `heightInPixels` should remain `0` since the widget content area has zero pixel height.
- In a stacked layout (e.g., column layout), minimizing widget A must cause widget B (the next widget below) to shift upward so its top position equals the minimized height of widget A. Maximizing A must restore B to its original position.
- After a complete minimize→maximize cycle, the widget's own context size (`height`, `width`, `heightInPixels`, `widthInPixels`) and the positions of sibling widgets must return to their pre-minimize values.

**Additional notes:**

- The issue likely involves how minimized height in layout units is computed and stored. The widget needs to remember its actual minimized header height (in layout units) separately from its expanded content height, and use this when reporting sizes to the context.
- The call to recompute layout positions after minimize/maximize must account for the minimized widget's *actual occupied height* in the layout (the header), not zero.
- Any direct manipulation of `this.minimized` and `this.height` during initialization or painting should be checked for ordering issues—resetting `this.minimized` to `false` or `null` early in the paint lifecycle could cause `_recomputeSize` or `_recomputeHeight` to use the wrong state branch before the real minimized status is later restored.

Improve handling of minimized widgets. Fix #159


```text
Bug: Session history search with scope="current" finds zero project transcripts on Windows

Description:
The `searchSessionHistory` function supports several scopes: "current" (the default), "project", and "all". On Windows the default "current" scope never returns matches for project transcripts, even when transcripts exist under `~/.claude/projects/`. Using `project="all"` (or explicitly specifying a project) works correctly, so the per-project directory lookup is the point of failure.

The cause is that the project path encoding used to locate the transcript storage directory under `~/.claude/projects/` does not handle the Windows drive letter. On Windows, an absolute path such as `C:\Users\me\project` contains a colon, which is illegal in a directory name. Claude Code’s actual transcript storage scheme sanitizes this drive colon by replacing it with a hyphen (together with path separators and dots), but the current encoding only replaces separators and dots, leaving the colon unchanged. That causes the search to look in a directory that does not exist, so it finds nothing.

Acceptance criteria:
- On Windows, when the session history search is performed with default scope (or explicitly `project` set to the current directory), project transcripts are discovered and returned, matching the behavior observed on macOS/Linux.
- The directory name derived from a Windows absolute path (e.g., `C:\Users\me\project`) must be the same name that Claude Code actually uses for that project’s transcript storage — that is, the drive colon must be replaced with a hyphen just as separators and dots are.
- The fix must not alter encoding of POSIX-style paths (no colon present); the existing behavior for Linux/macOS must remain unchanged.
- The encoding helper should be directly testable across platforms (e.g., made part of the module’s public exports) so that correctness can be verified deterministically, independent of the file system.
```

fix(session-search): strip drive colon so current-scope search finds transcripts on Windows (#3274)


```
Make `parseJsonc` accept trailing commas.

`parseJsonc` currently rejects valid JSONC that contains a trailing comma
before a closing brace or bracket.  Trailing commas are legal in many JSON
supersets (including JSONC) and should be tolerated.

Both `parseJsonc` and `stripJsoncComments` must handle trailing commas:
- `{"a":1,}` → `{"a":1}`
- `[1,2,]` → `[1,2]`
- nested structures, and commas followed by whitespace/newlines before `}` or `]`

Safety constraints:
- Commas inside string literals must never be removed or altered (e.g.
  `{"a":"1,"}`, `{"a":"x,}"}`, `{"a":"[1,2,]"} }` must all survive intact).
- Existing comment stripping behaviour must not break.  Trailing comments
  before a closing brace that also represent a trailing comma (e.g. `// comment`
  after `"a":1,`) should be handled correctly — the comma after the value
  must still be dropped.
```

fix(jsonc): tolerate trailing commas in JSONC config files


```
Title: HUD pane never appears during tmux launches

The OMC tool is supposed to show a live HUD status bar in a dedicated tmux pane when launching Claude Code inside tmux, but the pane is never created. The HUD rendering code already exists (in `src/hud/index.ts`) and works when invoked manually, but two things prevent it from being used automatically:

1. The `omc` CLI has no `hud` subcommand. There is no way to run the HUD renderer from the command line with a `--watch` loop that would be suitable for a persistent tmux split.
2. Some place in the launch machinery (`runClaude`) has a hardcoded flag or check that disables the HUD command, so even if the command existed, it would not be invoked.

Additionally, the HUD module currently calls `main()` at the top level, which means it runs on import. That's incompatible with programmatic reuse — a CLI command that needs to `import { main }` would trigger an extra run during the import itself.

We need to:
- Add an `omc hud [--watch] [--interval <ms>]` CLI command that imports and runs the HUD renderer. With `--watch` it should loop forever (polling at the given interval, default 1000 ms) so it can live in a tmux pane; without `--watch` it should just render once and exit.
- Refactor `src/hud/index.ts` to export its `main` function and only auto-execute when the module is run directly (e.g., using an `import.meta.url` guard), so that the CLI command can import it safely.
- In the launch logic, remove the hardcoded flag that prevents the HUD pane from being created. Ensure that when we're inside tmux, we build the appropriate HUD watch command and create the pane, reusing the existing `createHudWatchPane`/`listHudWatchPaneIdsInCurrentWindow` utilities.
- Stale HUD panes should be cleaned up before creating a new one (the existing `tmux-utils` helpers already handle that, provided they receive a non-empty command).

The result: launching OMC inside tmux should reliably create a HUD pane that updates every second.
```

fix(cli-launch): enable HUD pane by registering omc hud command


**Problem:** Custom tools (LSP, AST, and python_repl) are not available to Claude Code subagents

When a subagent is launched during an orchestration session, it cannot access any of the project's custom tools (`lsp_*`, `ast_grep_*`, `python_repl`). The subagent receives only the built-in tools and external MCP tools, but the LSP/AST/python_repl tools are registered outside any MCP server, so they are invisible to subagents. This forces the orchestrator to perform these operations itself instead of delegating them, defeating much of the parallelism and specialization that subagents are supposed to provide.

**Expected behavior:** All 15 custom tools (12 LSP, 2 AST, 1 python_repl) should be available to subagents via a dedicated in-process MCP server named `omc-tools`. Subagents should see them with the standard `mcp__omc-tools__<tool_name>` naming convention, and they should appear in each subagent's allow-list when the corresponding feature is enabled.

**Feature-flag behavior that must be preserved:**
- When `config.features.lspTools` is `false`, no `lsp_*` tools should appear in the allow-list (but the server itself can still exist)
- When `config.features.astTools` is `false`, no `ast_*` tools should appear in the allow-list
- `python_repl` is always included

**Additional constraint:** The server should run in-process (not as an external subprocess) since these tools are already local TypeScript code. The existing tool implementations in `src/tools/` should be reused, not duplicated. The external MCP server registration path that already works for servers like Exa and Context7 should continue to work alongside this new server.

**Impact:** Researchers, executors, and other subagents that need to navigate code, run AST searches, or execute Python snippets are blocked from doing their jobs. This is a regression from the parent project `oh-my-opencode`, where custom tools are available in subagent contexts.

feat(mcp): create in-process SDK MCP server for custom tools


## Feature Request: Configurable Analytics Visibility in HUD Statusline

### Summary

The HUD statusline currently always displays session cost and cache hit rate in analytics lines. Users should be able to independently toggle visibility of cost-related information and cache-related information across all HUD layouts.

### Acceptance Criteria

**1. Two new boolean configuration toggles must be added to the HUD element configuration:**

- `showCost` — controls whether dollar amounts, cost indicators, budget warnings, and cost-per-hour appear in analytics display lines. Defaults to `true`.
- `showCache` — controls whether cache hit rate and cache efficiency lines appear. Defaults to `true`.

**2. All preset configurations must include explicit values for both toggles:**

- The `minimal` preset should set both to `false` (since it's meant to show the least information).
- All other presets (`analytics`, `focused`, `full`, `opencode`, `dense`) should set both to `true`.
- The default configuration should set both to `true`.

**3. When `showCost` is `false`:**

- Cost amounts (e.g., `$1.2345`) should not appear in analytics lines.
- Cost color indicators (green/yellow/red) should not appear.
- Cost-per-hour information should not appear.
- Budget warnings should not appear anywhere in the statusline output.

**4. When `showCache` is `false`:**

- Cache hit rate percentage should not appear in analytics lines.
- Cache efficiency lines should not appear anywhere in the statusline output.

**5. Token counts should always be shown regardless of these toggles.** Token information is neither cost nor cache data, so it should remain visible even when both toggles are off.

**6. Top agent breakdown should always be shown when available in the analytics layout.** It is not governed by either toggle.

**7. Color indicators in analytics displays must use emoji circles consistently:**

- 🟢 for healthy/green status
- 🟡 for warning/yellow status
- 🔴 for critical/red status

Currently some analytics lines use plain bullet characters (`●`) instead of colored emoji circles. All analytics rendering paths should use these emoji indicators.

**8. The toggles must be respected in all HUD rendering paths:**

- The analytics-focused single-line layout (where session info and cost/token/cache/top agents are concatenated).
- The multi-line layout where session health analytics appear inline.
- The `full` and `dense` preset paths that add detail lines with full analytics.

**9. When cost information is hidden via `showCost`, any prefix indicating estimated costs (`~`) should also be suppressed since the cost value it decorates will not be shown.**

### Edge Cases

- **Empty/zero values:** When cache hit rate or cost is zero (or undefined), the display should still respect the toggle — showing `0.0%` or `$0.0000` only if the corresponding toggle is on, and omitting it entirely if off.
- **Missing session health:** When `SessionHealth` data is not available, the HUD should gracefully skip those analytics lines without errors, regardless of toggle state. This is existing behavior that must not regress.
- **Pipe separator consistency:** When items are conditionally included/excluded, the pipe-separated format should remain correct — no leading/trailing pipes, no double pipes.

### Non-Goals

- This feature does not change how cost or cache data is collected or computed — only whether it is displayed.
- No changes to the analytics storage, token tracking, or session health calculation are needed.

feat(hud): add showCache and showCost config toggles for analytics display


```text
Title: Bare repos produce wrong project identifier under OMC_STATE_DIR

Description:
When OMC_STATE_DIR is set, the project identifier is derived from the git
worktree root. In a bare repository, this identifier incorrectly uses the
*parent* directory’s name instead of the bare repo’s own name. If multiple
bare repos live next to each other (e.g. /opt/git/{repo-a.git, repo-b.git})
they can collide on the same state key because the resolver climbs up one
directory level.

This happens because the code that resolves the primary worktree root
interprets the output of `git --git-common-dir` and applies `dirname()` to
it unconditionally. In a normal (non-bare) repository, `--git-common-dir`
points to the `.git` directory, so `dirname()` correctly yields the
worktree root. In a bare repository, however, `--git-common-dir` returns
the bare repository root itself (no `.git` suffix) – e.g.
`/opt/git/my-project.git`. Calling `dirname()` on that path goes up to
`/opt/git`, losing the repository name.

We already have a guard for submodules (when `commonDir` contains
…/.git/modules). We need a similar guard for bare repos: only apply the
`dirname` override when `commonDir` actually ends with `.git` (i.e. the
resolved directory *is* a `.git` directory). For bare repos the returned
path should be used directly, so the project identifier stays based on the
bare repo’s own name.

Acceptance criteria:
- Bare repositories inside the same parent get distinct identifiers that
  include the bare repo directory name (e.g. `repo-a`, `repo-b`).
- The submodule skip (based on `.git/modules` in the path) continues to
  work.
- Normal (non-bare) repositories and linked worktrees are unaffected.
```

fix: guard against bare repo dirname resolution


## Feature Request: `omc update --clean` to force cache purge

### Problem

Running `omc update` every day never actually removes stale plugin cache versions. The update process refreshes the cache directory timestamps, which resets the 24-hour grace period on every run. Users who update frequently (e.g., daily) end up accumulating old cached plugin versions indefinitely, consuming disk space.

### Desired behavior

`omc update` should accept a `--clean` flag that forces immediate removal of stale plugin cache versions, bypassing the 24-hour modification-time grace period entirely.

### Acceptance criteria

1. **`omc update --clean`** removes stale plugin cache directories immediately, regardless of when they were last modified. Directories still referenced by active installations must never be removed.

2. **`omc update` (without `--clean`)** preserves the existing behavior: cache directories modified within the last 24 hours are skipped (race-condition guard), and only truly stale ones are purged.

3. **`omc update-reconcile`** (the internal subcommand invoked during updates) should also accept `--skip-grace-period` so the clean flag works correctly when reconciliation runs in a re-exec'd child process.

4. **The grace-period bypass must not create safety risks.** Active/in-use cache directories must still be protected from removal regardless of the flag. The flag only affects the mtime-based staleness check, not the active-reference check.

5. **Edge case:** If `--clean` is not passed, the grace period is enforced exactly as before. The flag is opt-in and changes nothing for existing callers.

### Non-goals

- This flag does not change which directories are considered "stale" — the logic for determining staleness based on the plugin registry references remains identical.

### Context

This is a quality-of-life feature for heavy users who run updates frequently. The existing `omc doctor` command may already have some cache-cleaning capability, but that requires a separate manual step and isn't integrated into the update flow.

feat(update): add --clean flag to bypass 24h cache purge grace period


**Title:** Agent delegation fails with 400 errors when using non-Claude providers (CC Switch, LiteLLM, etc.)

**Description**
I’m running `oh-my-claudecode` with tools like CC Switch or a custom proxy (LiteLLM) that redirect requests to non-Claude models (e.g., `glm-5`, `kimi-k2`, `MiniMax-Text-01`). When OMC spawns agents (executor, architect, etc.), the model tier names `sonnet`, `opus`, or `haiku` are passed as the model parameter to the Agent tool. These strings are Claude-specific and cause **400 Bad Request** errors from the non-Claude providers.

**Steps to reproduce**
1. Set environment variables to use a non-Claude provider, for example:
   `CLAUDE_MODEL=glm-5`
   (or set `ANTHROPIC_MODEL` to a non-Claude ID, or use a custom `ANTHROPIC_BASE_URL` such as `https://my-proxy.example.com/v1`)
2. Trigger any multi-agent pipeline (`/team`, `autopilot`, etc.) — the first delegated agent call fails with a provider-level 400 error.

**Expected behavior**
OMC should automatically detect when a non-Claude provider is in use and **enable the `routing.forceInherit` mode**. In that mode, agents do not receive any model tier parameter; instead they inherit the parent session’s currently configured model. This matches the existing `forceInherit` behavior and prevents passing incompatible model names to the provider.

**Edge cases / safety constraints**
- **Explicit user settings must be respected:** If the user has explicitly set `OMC_ROUTING_FORCE_INHERIT` to `true` or `false` (via env or config), the auto-detection should not override that choice.
- **Heuristic triggers:** A non-Claude provider should be assumed when:
  - The model ID in `CLAUDE_MODEL` or `ANTHROPIC_MODEL` does **not** contain the substring `claude` (case-insensitive).
  - The `ANTHROPIC_BASE_URL` does **not** contain `anthropic.com`.
- **Agent model injection:** When `forceInherit` is active (whether user-set or auto-enabled), the delegation enforcer must suppress any model parameter entirely, so the agent call truly inherits the parent model.

**Impact**
Without this, every user relying on a non-Claude backend (via CC Switch, LiteLLM, OneAPI, etc.) hits hard failures on every agent delegation. This makes OMC unusable with anything other than native Anthropic API access.

fix(config): auto-detect non-Claude providers and enable forceInherit (#1201) (#1202)


**Title:** Wiki session capture does not respect `CLAUDE_CONFIG_DIR` when reading configuration

**Description**
When users set the environment variable `CLAUDE_CONFIG_DIR` to a custom directory and place an `.omc-config.json` there with `wiki.autoCapture` set to `false`, session-end capture still creates wiki session logs. The problem is that the session-end hook always reads configuration from the default `~/.claude` directory instead of using the active configuration directory.

**Acceptance Criteria**

1. **Custom config dir is respected**
   - Given `CLAUDE_CONFIG_DIR` points to a custom directory containing `.omc-config.json` with `{ "wiki": { "autoCapture": false } }`
   - When a session ends
   - Then no session log entry is written to the wiki directory (the behavior matches the `autoCapture: false` setting).

2. **No regression when `CLAUDE_CONFIG_DIR` is unset**
   - With the variable not set, the session-end hook continues to read configuration from `~/.claude/.omc-config.json` exactly as before.

3. **Config resolution follows the project’s existing convention**
   - The method used to locate the active Claude configuration directory should be the same standard mechanism already used elsewhere in the codebase (e.g., for other hook or utility paths). No new environment variable handling logic should be duplicated.

**Additional context**
- A stale/unused import (`copyFileSync`) in the stdin symlink test file was noticed nearby and can be cleaned up while working in this area, but the primary focus is fixing the config resolution bug.

Respect custom Claude config when gating wiki session capture


Please make the OMC auto-updater handle native Windows (and other non‑npm) Claude Code installations gracefully.  Today, the updater assumes that a missing `@anthropic-ai/claude-code` npm package means Claude Code is absent and then tries to `npm install -g @anthropic-ai/claude-code` to restore it, which fails on native installers.

Instead, when the npm metadata is missing, first probe the actual `claude` binary: run `claude --version`, parse the version, and resolve its install path.  If a binary is found and its location is outside npm’s global tree, treat the install as “present but not restorable via npm” and skip the npm restore step completely.  Only positively‑npm‑identifiable installs (package.json exists, binary resides under npm root) should be eligible for npm restoration.  If we cannot determine the version or path, still treat it as non‑restorable—never fall back to a blind npm install in that case.

Keep the existing behaviour for npm‑managed installs unchanged; the only change is in the detection/classification logic so that native/manual installs do not trigger spurious npm failures.

Avoid npm restoration for native Claude installs (#3111)


Subject: HUD context percentage flickers on every refresh — needs display‑layer stabilization

The HUD statusline shows a `ctx:` percentage (plain text or bar) that changes between adjacent values (e.g. 54% ↔ 52% ↔ 54%) on successive polls, even when the real Claude Code context‑window usage hasn't moved meaningfully. This visual jitter makes the HUD harder to scan and distracts from genuinely important state changes.

Expected behaviour:

- Small refresh‑to‑refresh fluctuations (roughly ±2 percentage points) should be suppressed so the displayed value stays steady.
- The display **must** update immediately when the percentage crosses one of the configured thresholds (warning, “compact suggested”, critical) – those transitions are important signals and should not be delayed.
- The smoothing should only affect what the user *sees*; the underlying context‑percentage calculation (e.g. the logic in `src/hud/stdin.ts`) must not be altered.
- The displayed value should reset when the “scope” changes – for example, when the HUD switches to a different session or working directory – so that a new session doesn't inherit a stale smoothed value.
- To avoid showing a permanently frozen number, the HUD should fall back to the real current percentage after some reasonable timeout (e.g. ~5 seconds) if no threshold‑crossing update has occurred in the meantime.

Context:

The problem is in the HUD render layer – specifically in the functions that produce the `ctx:` segment and the bar variant inside `src/hud/elements/context.ts`. Both the plain text rendering (`renderContext`) and the bar rendering (`renderContextWithBar`) currently reflect the raw rounded percentage on every call, which creates the jitter. The fix should add a small display‑only hysteresis that is keyed by a session‑ or worktree‑scoped identifier (already available in the rendering pipeline) so that two unrelated HUD instances don’t interfere.

The relevant default thresholds (from the HUD config) are:
- `contextWarning`: usually 70%
- `contextCompactSuggestion`: usually 80%
- `contextCritical`: usually 85%

The smoothing window (±~2 pp) should apply inside each severity bucket; crossing any of those thresholds should bypass the hysteresis and update the display instantly.

Stabilize HUD context display across refreshes (#1821)