diff --git a/CLAUDE.md b/CLAUDE.md index e850b07..ae6e7e2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -81,7 +81,7 @@ Enums (`EAgentType`, `EAgentStatus`, `EReportType`) mirror .NET enums. **The dec - `MainApp` picks the layout by width: `>= DesktopView.minimumWidth` (900) → `DesktopView` (three columns side by side: Reports | LatestReports | Agents), otherwise `MobileView` (same three pages behind a `NavigationBar`). Both layouts reuse the *same* page widgets, so pages must not assume they own the whole screen. - **No state management package and no routing package.** Data loading is consistently: `StatefulWidget` → `late Future mXFuture` assigned in `initState` from `AcApi.instance...` → `FutureBuilder` with explicit loading / `snapshot.hasError` / empty-list branches. Reload = reassign the future inside `setState`. Navigation is plain `Navigator.of(context).push(MaterialPageRoute(...))`. -- **Automatic refresh**: every widget that shows api data polls the backend (it has no push channel) through the `AutoRefreshState` mixin (`lib/Widgets/AutoRefreshState.dart`): mix it into the `State` and implement `RefreshData`, which fetches again and, only on success, assigns the result as an already completed future. The mixin owns the timer, guards against overlapping requests and swallows errors of a tick. The interval (30 s) lives once in `AutoRefreshState.refreshInterval`. Because a refresh must not flash the spinner or drop the list, the `FutureBuilder`s branch on `snapshot.hasData` / `hasError`, **not** on `connectionState`. A refresh the user did not ask for stays silent — no snackbars, no disabled buttons (see `AgentDetails._reloadAgent`). +- **Automatic refresh**: every widget that shows api data polls the backend (it has no push channel) through the `AutoRefreshState` mixin (`lib/Widgets/AutoRefreshState.dart`): mix it into the `State` and implement `RefreshData`, which fetches again and, only on success, assigns the result as an already completed future. The mixin owns the timer, guards against overlapping requests and swallows errors of a tick. The interval (15 s) lives once in `AutoRefreshState.refreshInterval`. Because a refresh must not flash the spinner or drop the list, the `FutureBuilder`s branch on `snapshot.hasData` / `hasError`, **not** on `connectionState`. A refresh the user did not ask for stays silent — no snackbars, no disabled buttons (see `AgentDetails._reloadAgent`). - Icons per enum value live in extensions (`AgentTypeIcons`, `ReportTypeIcons` — `type.icon`) so every page renders a type identically. Human labels come from `displayName` on the enum itself. - `MarkdownText` is a hand-written markdown renderer (no `flutter_markdown` dependency) covering the subset the reports use: headings, lists, block quotes, fenced code, rules, and inline bold/italic/strikethrough/code/links. Extend it rather than adding a package. - Dependencies are intentionally minimal (`http`, `cupertino_icons`). Prefer solving things with Flutter's own widgets before adding a package. diff --git a/ac_web/lib/Widgets/AutoRefreshState.dart b/ac_web/lib/Widgets/AutoRefreshState.dart index 3b61208..c1d67dc 100644 --- a/ac_web/lib/Widgets/AutoRefreshState.dart +++ b/ac_web/lib/Widgets/AutoRefreshState.dart @@ -15,13 +15,13 @@ import 'package:flutter/material.dart'; /// /// A refresh happens quietly: implementations are expected to keep the data /// that is already on screen while the request runs, so that the user neither -/// sees a spinner nor loses the scroll position every half minute. Failures of +/// sees a spinner nor loses the scroll position every few seconds. Failures of /// a refresh are swallowed on purpose - the data on screen simply stays as it /// is and the next tick tries again. mixin AutoRefreshState on State { /// Interval in which the api data of the whole app is refreshed. - static const Duration refreshInterval = Duration(seconds: 30); + static const Duration refreshInterval = Duration(seconds: 15); Timer? _refreshTimer;