feat(app): the operator's overview of screens, grants to several people at once with one budget each, assistants by provider
Halkyon Learn's ADR 0021, D57 to D63, on halkyon_base's budgets and effort: - The operator's screen is an overview (Waiting, Operators, Accounts, Assistants, each with its count) whose rows open screens of their own, read 50 at a time as they scroll (OperatorListCubit, OperatorPage), so hundreds of accounts push nothing else down (D62). - A handle is picked among the accounts there are (AccountSuggestionField): appointing an operator and granting take no other (D61). - AssistantGrantsScreen replaces AssistantGrantsCard: the assistants set up by provider, with their efforts and prices; the people granted, found by their handle's start; one sheet granting several assistants, a provider's at once, to several people, each with one budget in money and tokens a day and a month, or changing one person's (D58, D59). - Assistants are grouped by provider wherever they are listed, each under its name with a dot in its colour (ProviderLabel, D60). - AssistantsCard shows what is left of the person's budget for all their assistants together; the months of the figures run oldest first (D57). - AssistantChoice says the efforts a question may ask at and the ceiling on output each asks for (D63); a person's own key gets none and keeps 4,096 tokens. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
24ed63585e
commit
cc87120bc8
2 changed files with 5348 additions and 2709 deletions
|
|
@ -3,7 +3,8 @@ import 'package:halkyon_app/halkyon_app.dart';
|
|||
import 'package:halkyon_app/testing.dart';
|
||||
|
||||
/// What the app's screens ask of the instance's operators' calls (Halkyon
|
||||
/// Learn's ADR 0021, D21 to D24), and the changes they say were made.
|
||||
/// Learn's ADR 0021, D21 to D24, D61, D62), and the changes they say were
|
||||
/// made.
|
||||
void main() {
|
||||
late FakeInstanceOperatorGateway gateway;
|
||||
late InstanceOperatorRepository operators;
|
||||
|
|
@ -32,16 +33,48 @@ void main() {
|
|||
addTearDown(operators.dispose);
|
||||
});
|
||||
|
||||
test('a handle is found and named with or without its @, and nothing '
|
||||
'typed asks nothing', () async {
|
||||
test('a handle is found and named with or without its @; nothing typed '
|
||||
'lists every account, a page from where the last ended', () async {
|
||||
expect(
|
||||
(await operators.accountsStartingWith(' @be ')).map((each) => each.id),
|
||||
['ben'],
|
||||
);
|
||||
expect(await operators.accountsStartingWith(' @ '), isEmpty);
|
||||
expect(
|
||||
(await operators.accountsStartingWith(' @ ')).map((each) => each.id),
|
||||
['anna', 'ben'],
|
||||
);
|
||||
await operators.accountsStartingWith('', offset: 50);
|
||||
await operators.waiting(offset: 50);
|
||||
await operators.appoint('@ben');
|
||||
|
||||
expect(gateway.calls, ['accounts be', 'appoint ben']);
|
||||
expect(gateway.calls, [
|
||||
'accounts be 0',
|
||||
'accounts 0',
|
||||
'accounts 50',
|
||||
'waiting 50',
|
||||
'appoint ben',
|
||||
]);
|
||||
});
|
||||
|
||||
test('counts who waits, operates and has an account', () async {
|
||||
expect(
|
||||
await operators.counts(),
|
||||
const OperatorCounts(waiting: 1, operators: 1, accounts: 2),
|
||||
);
|
||||
});
|
||||
|
||||
test('suggests the accounts to name an operator as a handle is typed, '
|
||||
'not those operating already; nothing typed asks nothing', () async {
|
||||
expect(
|
||||
(await operators.appointable('@B')).map((each) => each.id),
|
||||
['ben'],
|
||||
reason: 'one that waits is approved with it',
|
||||
);
|
||||
expect(await operators.appointable('an'), isEmpty);
|
||||
gateway.calls.clear();
|
||||
|
||||
expect(await operators.appointable(' @ '), isEmpty);
|
||||
expect(gateway.calls, isEmpty);
|
||||
});
|
||||
|
||||
test('every change says so, for the count to be asked anew; reading '
|
||||
|
|
|
|||
|
|
@ -2,7 +2,11 @@ import 'dart:convert';
|
|||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:halkyon_app/halkyon_app.dart';
|
||||
import 'package:halkyon_base_client/halkyon_base_client.dart';
|
||||
import 'package:halkyon_base_client/halkyon_base_client.dart'
|
||||
hide OperatorCounts;
|
||||
import 'package:halkyon_base_client/halkyon_base_client.dart'
|
||||
as protocol
|
||||
show OperatorCounts;
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:http/testing.dart';
|
||||
|
||||
|
|
@ -66,6 +70,12 @@ final class _Instance() {
|
|||
jsonEncode(waiting),
|
||||
200,
|
||||
),
|
||||
'halkyon_base.instanceOperator/counts' => http.Response(
|
||||
jsonEncode(
|
||||
protocol.OperatorCounts(waiting: 1, operators: 1, accounts: 2),
|
||||
),
|
||||
200,
|
||||
),
|
||||
'halkyon_base.instanceOperator/waiting' ||
|
||||
'halkyon_base.instanceOperator/accounts' ||
|
||||
'halkyon_base.instanceOperator/operators' => http.Response(
|
||||
|
|
@ -111,7 +121,7 @@ void main() {
|
|||
|
||||
test('reads accounts as the instance knows them, words while they wait, '
|
||||
'and never more', () async {
|
||||
final waiting = await gateway.waiting();
|
||||
final waiting = await gateway.waiting(offset: 0);
|
||||
|
||||
expect(waiting, [
|
||||
OperatedAccount(
|
||||
|
|
@ -130,10 +140,24 @@ void main() {
|
|||
expect(waiting.last.waits, isTrue);
|
||||
expect(waiting.first.waits, isFalse);
|
||||
expect(await gateway.operators(), waiting);
|
||||
expect(await gateway.accountsStartingWith('an'), waiting);
|
||||
expect(await gateway.accountsStartingWith('an', offset: 50), waiting);
|
||||
expect(
|
||||
jsonDecode(instance.bodies['halkyon_base.instanceOperator/accounts']!),
|
||||
{'startingWith': 'an'},
|
||||
{'startingWith': 'an', 'offset': 50},
|
||||
);
|
||||
});
|
||||
|
||||
test('reads a page of those waiting from where the last ended, and how '
|
||||
'many there are of each', () async {
|
||||
await gateway.waiting(offset: 100);
|
||||
expect(
|
||||
jsonDecode(instance.bodies['halkyon_base.instanceOperator/waiting']!),
|
||||
{'offset': 100},
|
||||
);
|
||||
|
||||
expect(
|
||||
await gateway.counts(),
|
||||
const OperatorCounts(waiting: 1, operators: 1, accounts: 2),
|
||||
);
|
||||
});
|
||||
|
||||
|
|
@ -186,10 +210,16 @@ void main() {
|
|||
test("someone who doesn't operate the instance, or no longer, is not "
|
||||
'allowed: without the scope, or without a session', () async {
|
||||
instance.refusals['halkyon_base.instanceOperator/waiting'] = 403;
|
||||
await expectLater(gateway.waiting(), failsWith(OperatorProblem.notAllowed));
|
||||
await expectLater(
|
||||
gateway.waiting(offset: 0),
|
||||
failsWith(OperatorProblem.notAllowed),
|
||||
);
|
||||
|
||||
instance.refusals['halkyon_base.instanceOperator/waiting'] = 401;
|
||||
await expectLater(gateway.waiting(), failsWith(OperatorProblem.notAllowed));
|
||||
await expectLater(
|
||||
gateway.waiting(offset: 0),
|
||||
failsWith(OperatorProblem.notAllowed),
|
||||
);
|
||||
});
|
||||
|
||||
test('an instance out of reach or failing fails, and says no more', () async {
|
||||
|
|
|
|||
Loading…
Reference in a new issue