Skip to content

Commit 064cb5f

Browse files
mrpilot2claude
andcommitted
docs: document notification system and ApplicationConfig
Add reference pages for the notification surfaces (manager, balloons, log view, banners, "Got it" tooltip, settings page) and for ApplicationConfig's feature toggles, and wire both into the nav. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent 49aab35 commit 064cb5f

4 files changed

Lines changed: 270 additions & 1 deletion

File tree

docs/core/notifications.md

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
# Notifications
2+
3+
aIDE's notification system covers four surfaces sharing one severity model (`NotificationType`) and one action model (`NotificationAction`):
4+
5+
| Surface | Logged? | Driven by | Auto-dismiss |
6+
|---|---|---|---|
7+
| **Balloon** | yes | `NotificationManager::post()` | transient balloons only |
8+
| **Notification log** (`NotificationView`) || reads the manager's history | never |
9+
| **Banner** | no | caller, direct widget use | caller-controlled |
10+
| **"Got it" tooltip** (`GotItTooltip`) | no | caller, direct widget use | on click / timeout |
11+
12+
Only the balloon path goes through `NotificationManagerInterface::post()` and is recorded in the log. Banners and "Got it" tooltips are caller-driven, ephemeral, and never logged.
13+
14+
**Headers:** `aide/notificationmanagerinterface.hpp`, `aide/notification.hpp`, `aide/notificationgroup.hpp`, `aide/gui/widgets/notificationballoon.hpp`, `aide/gui/widgets/banner.hpp`, `aide/gui/widgets/gotittooltip.hpp`, `aide/gui/widgets/notificationview.hpp`
15+
16+
---
17+
18+
## NotificationManagerInterface
19+
20+
Reachable via `app.notificationManager()` (or `ApplicationBuilder::notificationManager()`). Decouples posting/querying code from the concrete `NotificationManager`.
21+
22+
### Groups
23+
24+
Every notification belongs to a `NotificationGroup` — the unit configured on the settings page and used for routing:
25+
26+
```cpp
27+
aide::NotificationGroup group;
28+
group.id = aide::HierarchicalId("myapp.build");
29+
group.displayName = "Build";
30+
group.defaultDisplayType = aide::NotificationDisplayType::Balloon;
31+
group.logByDefault = true;
32+
33+
app.notificationManager().registerGroup(group);
34+
```
35+
36+
Register groups once, at startup, before posting to them.
37+
38+
### Posting
39+
40+
```cpp
41+
aide::Notification notification;
42+
notification.groupId = aide::HierarchicalId("myapp.build");
43+
notification.type = aide::NotificationType::Success;
44+
notification.title = "Build finished";
45+
notification.content = "0 errors, 3 warnings";
46+
notification.actions.push_back({"Open log", [] { /* ... */ }});
47+
48+
const auto id = app.notificationManager().post(std::move(notification));
49+
```
50+
51+
`post()` assigns `id` and `timestamp`, resolves and freezes the display type (see below), and appends the notification to the log. A `Notification` is an immutable snapshot once posted — it carries no read/dismissed flag; that lifecycle state lives in the manager.
52+
53+
### Display type resolution
54+
55+
`resolvedDisplayType(id)` is computed once, at `post()` time, and frozen — later settings changes never retroactively change how an already-posted notification displayed:
56+
57+
1. If Do Not Disturb is enabled, resolves to `None` (log-only).
58+
2. Otherwise, a per-group settings override (`notifications.<group-id>.displayType`), if the user set one.
59+
3. Otherwise, the group's `defaultDisplayType`.
60+
4. `None` if the group was never registered.
61+
62+
`NotificationDisplayType` values: `None`, `Balloon`, `StickyBalloon`. These are group-routed popup destinations only — editor banners, dialog banners and "Got it" tooltips are caller-driven surfaces and deliberately have no enumerator here.
63+
64+
### Querying and lifecycle
65+
66+
```cpp
67+
auto& mgr = app.notificationManager();
68+
69+
mgr.notifications(); // full log, for NotificationView
70+
mgr.notification(id); // std::optional<Notification>
71+
mgr.isRead(id);
72+
mgr.markRead(id); // per-notification
73+
mgr.markRead(); // global: clears hasUnread()
74+
mgr.hasUnread(); // boolean, NOT a count — bind to an opener button's unread dot
75+
mgr.remove(id);
76+
mgr.clearAll();
77+
78+
mgr.setDoNotDisturb(true);
79+
mgr.doNotDisturb();
80+
```
81+
82+
`hasUnread()` is intentionally boolean rather than a count (per an amendment to the original design): `NotificationView` clears it via `markRead()` whenever it becomes visible, and again on every post that arrives while still visible.
83+
84+
### Signals (concrete `NotificationManager`)
85+
86+
`NotificationManagerInterface` itself declares no signals — connect to the concrete `aide::NotificationManager` (as `ApplicationBuilder`/`Application` already do internally for the balloon host):
87+
88+
```cpp
89+
notificationPosted(aide::NotificationId id);
90+
notificationUpdated(aide::NotificationId id);
91+
notificationRemoved(aide::NotificationId id);
92+
notificationsCleared();
93+
unreadChanged(bool hasUnread);
94+
```
95+
96+
---
97+
98+
## Balloons
99+
100+
`NotificationBalloonHost` (wired automatically by `ApplicationBuilder`) listens for `notificationPosted` and shows a `NotificationBalloon` for every notification whose frozen `resolvedDisplayType` is `Balloon` or `StickyBalloon`; `None` is log-only and never reaches this surface.
101+
102+
- Transient balloons (`Balloon`) auto-dismiss after a fixed delay with a countdown progress bar that pauses on hover.
103+
- Sticky balloons (`StickyBalloon`) never time out — the user closes them explicitly.
104+
- Corner placement is one global setting (`notifications.balloonPlacement`, a `NotificationBalloonPlacement`: `BottomRight`, `BottomLeft`, `TopRight`, `TopLeft`). Stacking direction derives from that corner alone — balloons anchor there and grow toward screen center as more stack up.
105+
106+
You never construct `NotificationBalloon` directly — it's created and owned exclusively by `NotificationBalloonHost`.
107+
108+
---
109+
110+
## Notification log view
111+
112+
`aide::widgets::NotificationView` is a plain embeddable `QWidget` showing the notification log — a flat, newest-first timeline (JetBrains Notifications-style), **not** a docked tool window. It is in-memory and session-only: nothing is persisted, and the timeline starts empty on every launch.
113+
114+
```cpp
115+
auto* view = new aide::widgets::NotificationView(
116+
app.notificationManager(), *app.settingsProvider(), parent);
117+
118+
connect(view, &aide::widgets::NotificationView::settingsRequested, ...); // "Notification Settings..." entry
119+
connect(view, &aide::widgets::NotificationView::groupSettingsRequested, ...); // jump to a group's settings row
120+
```
121+
122+
Bind an unread indicator on whatever opener button toggles this view to `NotificationManagerInterface::hasUnread()`; cards render uniform, with no per-entry read/unread styling.
123+
124+
---
125+
126+
## Banners
127+
128+
`aide::widgets::Banner` is a severity-tinted inline strip (coloured left stripe, type icon, message, optional action links, close button) for embedding directly in a window — e.g. a dialog banner or an editor banner. It reuses `NotificationType`/`NotificationAction` but never goes through `post()` and is never logged.
129+
130+
```cpp
131+
auto* banner = new aide::widgets::Banner(
132+
aide::NotificationType::Warning, "Unsaved changes will be lost", parent);
133+
banner->addAction({"Save", [] { /* ... */ }});
134+
banner->setClosable(true);
135+
connect(banner, &aide::widgets::Banner::closed, banner, &QObject::deleteLater);
136+
```
137+
138+
Use banners for state tied to a specific window or editor, not for events that belong in the global notification log.
139+
140+
---
141+
142+
## "Got it" tooltip
143+
144+
`aide::widgets::GotItTooltip` is a one-shot onboarding tooltip anchored to a target widget, arrow pointing at it. Caller-driven: it never posts and never reaches the log, and depends only on `SettingsInterface` and the target widget — no `NotificationManager` dependency.
145+
146+
```cpp
147+
auto* tip = new aide::widgets::GotItTooltip(
148+
*app.settingsProvider(), "myapp.newFeatureButton", "Click here to try the new feature", parent);
149+
tip->withHeader("New!")
150+
.withLink("Learn more", [] { /* ... */ });
151+
152+
tip->showGotIt(targetButton, aide::widgets::GotItPosition::Below);
153+
```
154+
155+
- Seen state is an integer show-count stored at settings key `aide/gotit/<id>` (`canShow() == count < maxCount`, default `maxCount` of 1). `withShowCount()` raises the limit.
156+
- To reshow a changed tip, bump `id` (the `.v2` convention) — a fresh key starts its count at 0.
157+
- Pass `id` as a string literal: it is threaded through `HierarchicalId`, which stores a raw pointer rather than copying.
158+
- Only one tooltip shows at a time; closing one advances a single process-wide queue.
159+
- `showGotIt()` is a no-op (and deletes the tooltip) if `canShow()` is false or the target is null.
160+
161+
`GotItTooltip::isFirstRunAfterUpgrade(settings)` is a separate static helper: it compares the stored previous-run version against `QCoreApplication::applicationVersion()`, records the current version, and reports whether this run follows an upgrade — useful for gating "what's new" tooltips.
162+
163+
---
164+
165+
## Settings page
166+
167+
`aide::gui::NotificationsSettingsPage` is the built-in settings page (registered automatically by `ApplicationBuilder`): Do Not Disturb + balloon placement controls above a flat table with one row per registered `NotificationGroup`, letting users override each group's display type.
168+
169+
Settings keys (see `aide/core/settings/notifications/notificationsettingskeys.hpp`):
170+
171+
| Key | Meaning |
172+
|---|---|
173+
| `notifications.doNotDisturb` | global suppress-all toggle |
174+
| `notifications.balloonPlacement` | global corner anchor |
175+
| `notifications.<group-id>.displayType` | per-group override of `defaultDisplayType` |
176+
177+
---
178+
179+
## Demo reference
180+
181+
`demo/src/notificationlauncherdialog.{hpp,cpp}` (opened via the demo's "Demo → Notifications" menu action) exercises every surface end-to-end: balloon/sticky-balloon posting with and without actions, the "Got it" tooltip with position controls, dialog banners, and editor banners. Use it as a live reference for wiring each surface.
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# ApplicationConfig
2+
3+
`ApplicationConfig` is a consumer-facing set of feature toggles handed to `aide::Application` (or `ApplicationBuilder`) at construction time. It declares which of aIDE's built-in features the application uses.
4+
5+
**Header:** `aide/applicationconfig.hpp`
6+
7+
Every feature defaults to enabled, so a zero-configuration application behaves exactly like plain aIDE — with two exceptions (see below) that default to disabled.
8+
9+
---
10+
11+
## Passing a config at construction
12+
13+
```cpp
14+
aide::ApplicationConfig config;
15+
config.setEnabled(aide::ApplicationConfig::Feature::ShowLogInFileManagerAction, true);
16+
17+
const aide::Application app(argc, argv, config);
18+
```
19+
20+
`setEnabled()` returns `*this`, so calls chain fluently:
21+
22+
```cpp
23+
aide::ApplicationConfig config;
24+
config.setEnabled(aide::ApplicationConfig::Feature::ReportBugAction, true)
25+
.setEnabled(aide::ApplicationConfig::Feature::ViewFullscreenAction, false);
26+
```
27+
28+
The active config is readable back via `app.config()`.
29+
30+
---
31+
32+
## Available features
33+
34+
| `Feature` | Default | Description |
35+
|---|---|---|
36+
| `ViewFullscreenAction` | enabled | Checkable View → Full Screen action bound to F11 |
37+
| `ShowLogInFileManagerAction` | **disabled** | Help → "Show Log in \<File Manager\>" action |
38+
| `ReportBugAction` | **disabled** | Help → "Report Bug in aIDE" action |
39+
40+
`ShowLogInFileManagerAction` and `ReportBugAction` default to disabled because they expose concepts (the log file location, aIDE's own issue tracker) that end users of most aIDE-based applications have no reason to know about. Enable them for developer-facing tools.
41+
42+
```cpp
43+
[[nodiscard]] bool isEnabled(Feature feature) const;
44+
```
45+
46+
`isEnabled()` returns the consumer's override if one was set via `setEnabled()`, otherwise the feature's built-in default.
47+
48+
---
49+
50+
## Overriding the URL launcher
51+
52+
`ReportBugAction` (and any other feature that opens a URL) uses an OS-backed launcher by default. Substitute your own:
53+
54+
```cpp
55+
class MyUrlLauncher : public aide::UrlLauncherInterface
56+
{
57+
public:
58+
bool openUrl(const std::string& url) const override
59+
{
60+
// custom handling, e.g. routing through an embedded browser
61+
return true;
62+
}
63+
};
64+
65+
aide::ApplicationConfig config;
66+
config.setUrlLauncher(std::make_shared<MyUrlLauncher>());
67+
```
68+
69+
`urlLauncher()` returns `nullptr` if no override was set, in which case callers fall back to the default OS-backed launcher.
70+
71+
---
72+
73+
## Value semantics
74+
75+
`ApplicationConfig` is an ordinary copyable value with a private implementation (pImpl), so its binary layout stays stable as new toggles are added — extending it is an additive enum value plus a default-table entry, never a public layout change. Copies deep-copy their overrides, so a copy can be modified without affecting the original.

docs/fundamentals/application-lifecycle.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,23 @@ aide::Application::setApplicationDisplayName("My Application"); // optional, for
2727
const aide::Application app(argc, argv);
2828
```
2929

30+
To opt into or out of built-in features, pass an [`ApplicationConfig`](application-config.md):
31+
32+
```cpp
33+
aide::ApplicationConfig config;
34+
config.setEnabled(aide::ApplicationConfig::Feature::ReportBugAction, true);
35+
36+
const aide::Application app(argc, argv, config);
37+
```
38+
3039
During construction, `Application` internally creates and wires:
3140
3241
- **Logger** — spdlog-backed; log files are written to the platform's standard app data location
3342
- **Settings stores** — two `SettingsInterface` instances (versionable and unversionable) backed by `QSettings`
3443
- **Main window** — a `QMainWindow` with pre-built `File` and `Help` menus
3544
- **Action registry** — populated with the built-in actions (`FILE_SETTINGS`, `FILE_QUIT`, `HELP_ABOUT_AIDE`, `HELP_ABOUT_QT`)
36-
- **Settings dialog** — including the built-in keymap settings page
45+
- **Settings dialog** — including the built-in keymap and [notifications](../core/notifications.md) settings pages
46+
- **Notification manager** and balloon host — see [Notifications](../core/notifications.md)
3747
3848
---
3949
@@ -46,6 +56,7 @@ auto mainWindow = app.mainWindow(); // shared_ptr<QMainWindow>
4656
auto actionReg = app.actionRegistry(); // shared_ptr<ActionRegistryInterface>
4757
auto settings = app.settingsProvider(); // shared_ptr<AideSettingsProvider>
4858
auto translator = app.translator(); // shared_ptr<TranslatorInterface>
59+
auto& notifManager = app.notificationManager(); // NotificationManagerInterface&
4960
```
5061

5162
The logger is accessed statically (because it is also used before the app object exists):

mkdocs.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ nav:
6767
- Fundamentals:
6868
- HierarchicalId: fundamentals/hierarchical-id.md
6969
- Application Lifecycle: fundamentals/application-lifecycle.md
70+
- ApplicationConfig: fundamentals/application-config.md
7071
- Core Concepts:
7172
- Action Registry: core/action-registry.md
7273
- Appearance: core/appearance.md
@@ -75,6 +76,7 @@ nav:
7576
- Adding Settings Pages: core/settings/adding-settings-pages.md
7677
- Key Bindings: core/key-bindings.md
7778
- Logging: core/logging.md
79+
- Notifications: core/notifications.md
7880
- UI Components:
7981
- Main Window: ui/main-window.md
8082
- Widgets:

0 commit comments

Comments
 (0)