77 lines
7.0 KiB
Markdown
77 lines
7.0 KiB
Markdown
# CLAUDE.md
|
|
|
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
|
|
## Project
|
|
|
|
Flutter frontend ("AC Control") for the AcAPI, a .NET Web API backend of the "Artificial Company".
|
|
The UI shows AI agents, their tasks/availability, and the reports they produce.
|
|
|
|
The Flutter project lives in `ac_web/`, not at the repository root. **All commands below must be run from `ac_web/`.**
|
|
|
|
## Commands
|
|
|
|
```bash
|
|
flutter pub get # install dependencies
|
|
flutter run -d chrome # run in the browser (primary target)
|
|
flutter run -d linux # run as a desktop app
|
|
flutter analyze # lint / static analysis (flutter_lints)
|
|
flutter test # run all tests
|
|
flutter test test/Api/ApiLayer_test.dart # single test file
|
|
flutter test test/Api/ApiLayer_test.dart --plain-name 'parses agents' # single test
|
|
flutter build web
|
|
```
|
|
|
|
Flutter 3.44 / Dart 3.12 (`sdk: ^3.12.2`).
|
|
|
|
### Tests
|
|
|
|
Test files **must end in `_test.dart`** or `flutter test` silently ignores them. The project keeps its PascalCase file naming in front of that suffix: `test/Api/ApiLayer_test.dart`, `test/Layouts/MainApp_test.dart`.
|
|
|
|
Widget tests run against a mocked backend: build a `MockClient` that answers by route, hand it to `AcApi.configure(..., httpClient: client)` in `setUp`, and `AcApi.reset()` in `tearDown`. Do **not** use `pumpAndSettle` on pages that load data — the loading state is a `CircularProgressIndicator`, whose animation never settles; pump a couple of frames instead (see `settleApiCalls` in `MainApp_test.dart`). Layout tests set `tester.view.physicalSize` to cross the 900px desktop/mobile breakpoint. To test the automatic refresh, pump `AutoRefreshState.refreshInterval` and then a few frames (`test/Widgets/AutoRefresh_test.dart`).
|
|
|
|
`flutter analyze` reports ~40 pre-existing `file_names` / `non_constant_identifier_names` infos caused by the naming conventions below. They are expected — only care about new warnings and errors.
|
|
|
|
## Backend connection
|
|
|
|
The backend host/port is configured once in `lib/main.dart` via `AcApi.configure(const ApiConfig(host: 'localhost', port: 5118))`. Plain http is intentional — the API is only reached inside a trusted network. There is no environment/config file; changing the backend address means editing `main.dart`.
|
|
|
|
## Architecture
|
|
|
|
### API layer (`lib/Api/`)
|
|
|
|
Three strictly separated levels — keep them that way:
|
|
|
|
1. **`ApiClient`** — transport only. Builds requests, applies the timeout, maps every failure onto a sealed `ApiException` subtype (`ApiHttpException`, `ApiNetworkException`, `ApiTimeoutException`, `ApiParsingException`), decodes json. Knows nothing about controllers.
|
|
2. **`Services/*Service extends ApiServiceBase`** — one class per backend controller, exposing `controllerRoute` (e.g. `agents`) and typed methods that call `client.getList` / `getObject` with a dto `fromJson`.
|
|
3. **`AcApi`** — the single entry point. Holds the shared instance (`AcApi.instance`), lazily creates services, and is reconfigurable via `AcApi.configure(...)` / `AcApi.reset()` (tests inject a `MockClient` through the `httpClient` parameter).
|
|
|
|
`lib/Api/Api.dart` is a barrel file; UI code should `import 'package:ac_web/Api/Api.dart';`.
|
|
|
|
**Adding a backend controller**: create the dtos under `Api/Dtos`, add a `<Name>Service extends ApiServiceBase`, expose it as a lazy getter on `AcApi`, and export both from `Api.dart`.
|
|
|
|
### DTOs and .NET interop (`lib/Api/Dtos/`, `lib/Api/JsonReader.dart`)
|
|
|
|
The backend is still in development, so parsing is deliberately defensive. Never call `json['key']` directly in a dto — always go through `JsonReader`, which resolves keys case-insensitively (PascalCase from .NET vs. camelCase), tolerates missing properties, and parses enums that may arrive as either a name or a numeric index.
|
|
|
|
Enums (`EAgentType`, `EAgentStatus`, `EReportType`) mirror .NET enums. **The declaration order must match the .NET enum** because values may be serialized as indices, and `unknown` is always the last value so it never shadows a real backend value and acts as the fallback for values this client does not know yet. `EReportType.wireName` carries the SCREAMING_SNAKE .NET spelling.
|
|
|
|
### UI (`lib/Layouts/`, `lib/Pages/`, `lib/Widgets/`)
|
|
|
|
- `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<T> 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`).
|
|
- 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.
|
|
|
|
## Conventions
|
|
|
|
These deviate from stock Dart style; match the surrounding code rather than the default lints:
|
|
|
|
- Files and directories are **PascalCase** (`lib/Widgets/AgentTaskCard.dart`), one public class per file, named after the file.
|
|
- Instance fields are prefixed with `m` (`mAgent`, `mReportsFuture`, `mIsReloading`); private helpers keep the leading underscore (`_buildList`, `_openType`).
|
|
- Public methods on `State` classes are **PascalCase** (`ReloadAgents`, `ReloadEntries`); private build helpers are camelCase with `_`.
|
|
- Enums are prefixed `E`, dtos suffixed `Dto`.
|
|
- Comments explain *why* (e.g. why enum order matters, why http is fine) in full sentences — keep that density.
|