feat(learn): the assistant's field spans the screen, its buttons boxed and still as who answers changes

Following the developer's review of 3 Nov 2026 (ADR 0021, D67):

- Unfolded in the Library, the field spans the screen between its margins,
  as on Home and a studyset: it fell short on the right, by the padding
  around the two folded buttons, which now stands only around those.
- Who answers stands right before the buttons at the right, which no longer
  shift as its name changes (Claude Haiku, Claude Opus): it fills the space
  left, aligned to its end, rather than a share of it.
- Every button of the field and of a conversation's follow-up is boxed, a
  tile a shade above the field with rounded corners, as Claude's apps have
  them; who answers too.
- On a studyset with a licence, the unfolded field takes its usual place
  over the licence's line rather than squeezing in above it; folded, its
  row still keeps clear of the line.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-11-03 21:50:00 +01:00
commit 96b1a0fc64
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
2 changed files with 209 additions and 64 deletions

View file

@ -465,6 +465,53 @@ void main() {
debugDefaultTargetPlatformOverride = null;
});
for (final (where, location) in [
('Home', RoutePaths.home),
('the Library', RoutePaths.library),
('a studyset', RoutePaths.studyset('japanese')),
]) {
testWidgets('unfolded on $where, the field spans the width between the '
"screen's margins, as wide on the right as on the left (the "
"developer's review of 3 Nov 2026)", (tester) async {
await showApp(tester, location: location);
await unfold(tester);
final field = tester.getRect(find.byType(AssistantField));
expect(field.left, closeTo(16, 0.01));
expect(field.right, closeTo(400 - 16, 0.01));
});
}
testWidgets('on a studyset with a licence, the folded button keeps clear '
'of its line; unfolded, the field takes its usual place over it rather '
"than squeezing in above it (the developer's review of 3 Nov 2026)", (
tester,
) async {
studysets.receiveRemoteChange(
'japanese',
encodeStudysetPayload(
StudysetContent(
title: 'Japanese',
description: RichContent.empty(),
license: 'CC-BY-SA-4.0',
licenseHolder: 'Tae Kim',
licenseYear: 2026,
),
),
);
await showApp(tester, location: RoutePaths.studyset('japanese'));
final licence = find.textContaining('Tae Kim', findRichText: true);
final line = tester.getRect(licence);
expect(
tester.getRect(find.byTooltip('Search the web')).bottom,
lessThan(line.top),
reason: 'folded, clear of the line',
);
await unfold(tester);
final field = tester.getRect(find.byType(AssistantField));
expect(field.bottom, greaterThan(tester.getRect(licence).top));
});
testWidgets("a studyset's name is never offered to go into a web search", (
tester,
) async {

View file

@ -316,6 +316,60 @@ void main() {
);
});
testWidgets('the buttons right of who answers stay where they are '
'whatever its name, and every button of the field is boxed, as '
"Claude's (the developer's review of 3 Nov 2026)", (tester) async {
assistants.offer = AssistantsOffer(
assistants: [
_claude(),
OfferedAssistant(
id: 'opus',
name: 'Claude Opus, thinking longer',
protocol: 'anthropic',
host: 'api.anthropic.com',
model: 'claude-opus',
agreedAt: _agreed,
),
],
);
await showApp(tester);
Rect at(String tooltip) => tester.getRect(
find.descendant(
of: find.byType(AssistantField),
matching: find.byTooltip(tooltip),
),
);
final history = at('Conversations');
final speak = at('Speak your question');
final field = tester.getRect(find.byType(AssistantField));
expect(field.right - speak.right, lessThan(16), reason: 'at the right');
await openPicker(tester);
await tester.tap(find.text('Claude Opus, thinking longer').last);
await tester.pumpAndSettle();
expect(find.text('Ask Claude Opus, thinking longer…'), findsOneWidget);
expect(at('Conversations'), history);
expect(at('Speak your question'), speak);
for (final icon in [
LucideIcons.plus,
LucideIcons.history,
LucideIcons.mic,
]) {
final button = tester.widget<IconButton>(
find.descendant(
of: find.byType(AssistantField),
matching: find.widgetWithIcon(IconButton, icon),
),
);
expect(
button.style?.backgroundColor?.resolve({}),
isNotNull,
reason: '$icon boxed',
);
}
});
testWidgets("an answer's Copy stands beneath it, its icon in line with "
"the answer's text", (tester) async {
await showApp(tester);
@ -750,7 +804,8 @@ void main() {
await unfold(tester);
await choose(tester, ['Studyset: Diary']);
await ask(tester, 'What is this about?');
// Short: the test's wide font would hang the text's handle over Ask.
await ask(tester, 'About what?');
expect(find.text(warning), findsOneWidget);
expect(find.textContaining('to api.anthropic.com'), findsOneWidget);
@ -758,7 +813,7 @@ void main() {
await tester.tap(find.text('Cancel'));
await tester.pumpAndSettle();
expect(assistants.asked, isEmpty);
expect(find.text('What is this about?'), findsOneWidget);
expect(find.text('About what?'), findsOneWidget);
assistants.answers.add(_says('Your diary.'));
await tester.tap(