Skip to content

Bug: multiple notifications panel issues — dismiss on outside click, empty state, fade animation, inline styles, theme compliance #86

Closed

Description

@JoshuaRowePhantom

Summary

The notifications panel has several related issues that should be fixed together:


1. Panel does not dismiss when clicking outside

NotificationsViewModel.IsOpen is only toggled via ToggleOpenCommand (bound to the bell button). Clicking anywhere outside the panel does not close it. Expected: a click outside the panel (anywhere in the window not inside the panel) dismisses it, similar to a dropdown/flyout.

Suggested fix: subscribe to pointer-pressed events on the window (or use a transparent hit-test overlay behind the panel) and set IsOpen = false when a press is detected outside the panel bounds. Alternatively, host the panel in an Avalonia Popup or Flyout which handles outside-click dismissal natively.


2. No empty state message

When there are no notifications, the panel shows a blank scroll area. It should instead display a centred message: "There are no notifications."

Suggested fix: add an empty-state TextBlock in the panel (or a NotHasRows property on NotificationsViewModel) that is shown when Rows.Count == 0.


3. "Becoming transparent" animation does not cause the panel to disappear

There is currently no fade/transparency animation at all — IsVisible is bound directly to IsOpen with an instant show/hide. If a "becoming transparent" animation is introduced (e.g. an opacity transition), it must ultimately set IsVisible = false (or IsOpen = false) at the end so the panel is fully removed from the visual tree, not left as a transparent-but-present overlay that intercepts input.

Suggested fix: use an Avalonia Animation or Transition on Opacity triggered by IsOpen, combined with setting IsHitTestVisible="False" at opacity 0 and IsVisible="False" once the animation completes; or use a Popup/Flyout whose IsOpen drives both the animation and the visibility.


4. Notification panel styling is embedded directly in MainWindow.axaml

All visual properties of the panel — Background="#E0202020", CornerRadius, BoxShadow, hardcoded Width, MaxHeight, Padding, font weights and opacities — are inlined in MainWindow.axaml rather than defined in the shared style library. This bypasses the theming system and makes the panel invisible to theme changes.

Suggested fix:

  • Extract the entire notifications panel into a dedicated NotificationsControl (UserControl) in Phantom.Workspaces.
  • Move all style properties into Phantom.Workspaces.Gui.Styles (under a notifications-panel class or equivalent), using theme resource references (e.g. {DynamicResource ...}) for colours, opacities, and shadows rather than hardcoded hex values.

5. Styling does not obey the theme

Related to #4: Background="#E0202020" and Foreground="Gold" (on the unread count badge) are hardcoded colours that do not respond to light/dark theme switching or accent colour changes.

Suggested fix: replace all hardcoded colour values with {DynamicResource} references to the appropriate theme brush keys, consistent with how other panels in the app are styled.


Files

  • Phantom.Workspaces/MainWindow.axaml (lines 176–222) — notification panel markup
  • Phantom.Workspaces/ViewModels/NotificationsViewModel.cs — IsOpen, Rows, ToggleOpen
  • Phantom.Workspaces.Gui.Styles/ — no notification styles currently exist here; they should be added

Activity

  1. JoshuaRowePhantom commented on Jun 26, 2026

    @JoshuaRowePhantom
    OwnerAuthor

    Additional issue: panel is obscured by DockControl despite ZIndex=100

    Screenshot: The notification panel is partially or fully obscured by the workspace dock content even though the panel has ZIndex="100" set.

    Root cause: The notification Border is declared as a sibling of dock:DockControl inside <Grid Grid.Row="1" ColumnDefinitions="*,Auto,2*"> (MainWindow.axaml line 176). Both occupy Grid.Column="2". Avalonia Grid ZIndex only governs draw order among siblings in the same visual layer, but Dock.Avalonia manages its own internal layering — dock tabs, overlays, and drag-handles render in their own sub-tree — so they paint on top of a ZIndex-elevated sibling in the parent Grid.

    Correct fix: Host the notifications panel in an Avalonia Popup control (which renders in its own top-level window layer, always above all other controls) rather than as a ZIndex-elevated Border. This also resolves issue #1 in this bug (outside-click dismissal) natively, because Popup has built-in LightDismissOverlayMode.

    Concretely:

    <!-- In MainWindow.axaml, inside the notification button Panel (Grid.Column="4") -->
    <Popup IsOpen="{Binding NotificationsViewModel.IsOpen}"
           PlacementMode="Bottom"
           PlacementTarget="{x:Reference NotificationBellButton}"
           LightDismissOverlayMode="OnTop"
           IsLightDismissEnabled="True">
        <!-- move the current Border/Grid content here -->
    </Popup>

    LightDismissOverlayMode="OnTop" closes the popup on any outside click and keeps it above the dock layer. The PlacementTarget must reference the bell Button (give it x:Name="NotificationBellButton").

    This change should be done alongside the extract-to-NotificationsControl refactor (issue #4 above) so the popup content and its light-dismiss behaviour are encapsulated together.

  2. JoshuaRowePhantom commented on Jun 26, 2026

    @JoshuaRowePhantom
    OwnerAuthor

    Design

    Root causes

    1. Outside-click dismissal: IsOpen is only toggled programmatically via ToggleOpenCommand. The panel has no click-away or focus-loss dismissal.
    2. No empty state: When Rows.Count == 0, the panel displays an empty ScrollViewer instead of a "no notifications" message.
    3. Animation/visibility: Currently IsVisible binds directly to IsOpen with no animation. Any future opacity animation must ultimately set the panel to non-visible to avoid intercepting input.
    4. ZIndex limitation: The notification Border is a sibling of dock:DockControl in Grid.Column="2". Dock.Avalonia internal layering renders dock elements on top of ZIndex-elevated siblings.
    5. Hardcoded styles: Background="#E0202020", Foreground="Gold" (badge), CornerRadius, BoxShadow, and other properties are inlined in MainWindow.axaml, bypassing the theme system.

    Approach

    Extract to control + Popup hosting:

    Empty state:

    • Add HasRows property to NotificationsViewModel that returns Rows.Count > 0.
    • Raise PropertyChanged in RefreshRows() so the UI updates when notifications change.
    • In NotificationsControl.axaml, conditionally show a centered "There are no notifications." TextBlock when HasRows == false.

    Theme-compliant styles:

    • Add Phantom.Workspaces.Gui.Styles\Styles\NotificationsStyles.axaml with resource definitions:
      • Theme.Notifications.Panel.Background → replaces #E0202020
      • Theme.Notifications.Panel.CornerRadius → replaces hardcoded 6
      • Theme.Notifications.Panel.BoxShadow → replaces hardcoded shadow
      • Theme.Notifications.Badge.Foreground → replaces Gold
      • Other spacing/opacity resources as needed
    • Reference {DynamicResource ...} in NotificationsControl.axaml so styles respond to theme changes.
    • Include the new styles file in SharedStyles.axaml (or the App.axaml if that's the pattern).

    Files to change

    1. Phantom.Workspaces\ViewModels\NotificationsViewModel.cs — add HasRows property
    2. Phantom.Workspaces\Controls\NotificationsControl.axaml — new UserControl (extract panel markup)
    3. Phantom.Workspaces\Controls\NotificationsControl.axaml.cs — codebehind (minimal/empty)
    4. Phantom.Workspaces\MainWindow.axaml — replace Border with Popup hosting NotificationsControl
    5. Phantom.Workspaces.Gui.Styles\Styles\NotificationsStyles.axaml — new theme resource definitions
    6. Phantom.Workspaces.Gui.Styles\Styles\SharedStyles.axaml — include NotificationsStyles.axaml
    7. Phantom.Workspaces.Tests\NotificationsViewModelTests.cs — new test file for HasRows property tests

    No design doc updates needed (this is a UI implementation fix, not an architecture change).

    Design decisions

    • Why Popup over custom overlay? Popup provides native light-dismiss, top-layer rendering, and placement logic. Less code, fewer bugs, standard Avalonia pattern.
    • Why extract to UserControl? Encapsulates the panel content, makes styling/testing easier, consistent with EntityCardControl pattern in the codebase.
    • Why DynamicResource over StaticResource? Enables live theme switching without restarting the app.
  3. JoshuaRowePhantom commented on Jun 26, 2026

    @JoshuaRowePhantom
    OwnerAuthor

    Tests

    Unit Tests (Phantom.Workspaces.Tests\NotificationsViewModelTests.cs)

    1. HasRows_WithNoRows_ReturnsFalse

      • Verifies HasRows returns alse when Rows collection is empty
      • Belongs to: Phantom.Workspaces.Tests
    2. HasRows_WithRows_ReturnsTrue

      • Verifies HasRows returns rue when Rows collection has at least one item
      • Belongs to: Phantom.Workspaces.Tests
    3. HasRows_RaisesPropertyChanged_WhenRowsChange

      • Verifies PropertyChanged event is raised for HasRows when RefreshRows() is called and the row count changes
      • Belongs to: Phantom.Workspaces.Tests

    Integration Tests (not required for this issue)

    • Popup light-dismiss behavior is built into Avalonia and doesn't need custom tests
    • Theme resource resolution will be verified by existing Avalonia infrastructure
    • Empty state rendering can be validated manually (no headless UI test needed for simple conditional visibility)

    All tests follow the Method_Condition_ExpectedResult naming convention and test the ViewModel logic in isolation.

  4. added a commit that references this issue on Jun 27, 2026
    bed3ef4
  5. JoshuaRowePhantom commented on Jun 30, 2026

    @JoshuaRowePhantom
    OwnerAuthor

    Verification passed

    Checked: commit bed3ef4 (Fix #86) on the features branch. Files inspected:

    • Phantom.Workspaces/ViewModels/NotificationsViewModel.cs
    • Phantom.Workspaces/Controls/NotificationsControl.axaml + .axaml.cs
    • Phantom.Workspaces/MainWindow.axaml
    • Phantom.Workspaces.Gui.Shared/Styles/NotificationsStyles.axaml
    • Phantom.Workspaces.Tests/NotificationsViewModelTests.cs
    • Phantom.Workspaces.Gui.Shared.Tests/NotificationsStylesTests.cs

    Result: Implementation found, all verification criteria satisfied, tests present.

    Features verified:

    1. Outside-click dismissal: panel hosted in Popup with IsLightDismissEnabled=True; IsOpen is TwoWay-bound.
    2. ZIndex vs Dock.Avalonia: resolved by Popup rendering in its own top-level OS window layer.
    3. Empty state message: NotificationsControl.axaml shows 'There are no notifications.' when HasRows == false.
    4. HasRows property: added to NotificationsViewModel; RaisePropertyChanged called in RefreshRows().
    5. Extract to UserControl: NotificationsControl.axaml encapsulates panel content.
    6. Theme-compliant styles: NotificationsStyles.axaml defines Theme.Notifications.* resources; DynamicResource used throughout; no hardcoded hex values remain in MainWindow.axaml.
    7. Tests: HasRows_WithNoRows_ReturnsFalse, HasRows_WithRows_ReturnsTrue, HasRows_RaisesPropertyChanged_WhenRowsChange, plus extensive additional ViewModel and style coverage.
    8. No disabled/quarantined tests, no unresolved TODOs, no timing-dependent tests.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingverifiedIssue has been verified

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions