Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/workflows/main_deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,14 @@ jobs:
# automated and assistive-tech testing can read the canvas. NOT set on
# production (release.yaml), where semantics stays on-demand per user.
echo "ENABLE_SEMANTICS=true" >> build/web/.env
# Staging only: enable the best-effort analytics dual-write to the
# teacher-BFF (admin-dash-api). BOTH must be set for it to fire (no-op if
# either is missing). Points at the STAGING BFF; NOT set on production
# (release.yaml) until validated on staging. The BFF's CORS allowlist must
# include this web origin (app.staging.pangea.chat) — see admin-dash-api
# task_definition_staging.json CORS_ORIGINS.
echo "ANALYTICS_DUAL_WRITE_ENABLED=true" >> build/web/.env
echo "TEACHER_BFF_API=https://admin-dash-api.staging.pangea.chat" >> build/web/.env
touch build/web/assets/envs.json
echo "$ENV_OVERRIDES" >> build/web/assets/envs.json
mkdir -p build/web/.well-known
Expand Down
15 changes: 6 additions & 9 deletions lib/routes/world/left_panel/left_panel_courses_list_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ List<Room> joinedCourses(Client client, L10n l10n) =>
client.rooms
.where(
(r) =>
r.isSpace && r.membership == Membership.join && r.coursePlan != null,
r.isSpace &&
r.membership == Membership.join &&
r.coursePlan != null,
)
.toList()
..sort(
Expand Down Expand Up @@ -63,8 +65,7 @@ class CoursesHubPanel extends StatelessWidget {
// With courses present, the three add-course actions ride the
// header as right-justified icons; when empty they stay as full
// buttons in the body below (the empty state).
trailing:
courses.isEmpty ? null : const AddCourseHeaderActions(),
trailing: courses.isEmpty ? null : const AddCourseHeaderActions(),
),
Expanded(child: LeftPanelCoursesListView(courses: courses)),
],
Expand Down Expand Up @@ -97,9 +98,7 @@ class LeftPanelCoursesListView extends StatelessWidget {
// "Add new course" section divider + the full add-course buttons.
Row(
children: [
Expanded(
child: Divider(color: theme.colorScheme.outlineVariant),
),
Expanded(child: Divider(color: theme.colorScheme.outlineVariant)),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0),
child: Text(
Expand All @@ -109,9 +108,7 @@ class LeftPanelCoursesListView extends StatelessWidget {
),
),
),
Expanded(
child: Divider(color: theme.colorScheme.outlineVariant),
),
Expanded(child: Divider(color: theme.colorScheme.outlineVariant)),
],
),
const SizedBox(height: 12.0),
Expand Down
24 changes: 12 additions & 12 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -293,10 +293,10 @@ packages:
dependency: "direct main"
description:
name: characters
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
url: "https://pub.dev"
source: hosted
version: "1.4.0"
version: "1.4.1"
charcode:
dependency: transitive
description:
Expand Down Expand Up @@ -1496,18 +1496,18 @@ packages:
dependency: transitive
description:
name: matcher
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
url: "https://pub.dev"
source: hosted
version: "0.12.17"
version: "0.12.19"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
url: "https://pub.dev"
source: hosted
version: "0.11.1"
version: "0.13.0"
material_symbols_icons:
dependency: "direct main"
description:
Expand Down Expand Up @@ -2438,26 +2438,26 @@ packages:
dependency: transitive
description:
name: test
sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7"
sha256: "280d6d890011ca966ad08df7e8a4ddfab0fb3aa49f96ed6de56e3521347a9ae7"
url: "https://pub.dev"
source: hosted
version: "1.26.3"
version: "1.30.0"
test_api:
dependency: transitive
description:
name: test_api
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
url: "https://pub.dev"
source: hosted
version: "0.7.7"
version: "0.7.10"
test_core:
dependency: transitive
description:
name: test_core
sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0"
sha256: "0381bd1585d1a924763c308100f2138205252fb90c9d4eeaf28489ee65ccde51"
url: "https://pub.dev"
source: hosted
version: "0.6.12"
version: "0.6.16"
timezone:
dependency: transitive
description:
Expand Down
25 changes: 13 additions & 12 deletions test/pangea/grammar_error_example_split_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ void main() {
group('GrammarErrorExampleWidget.splitAroundBlank', () {
test('blanks exactly the target word, not the next one', () {
// "El gato come" — target "gato" at grapheme offset 3, length 4.
final s = GrammarErrorExampleWidget.splitAroundBlank('El gato come', 3, 4);
final s = GrammarErrorExampleWidget.splitAroundBlank(
'El gato come',
3,
4,
);
expect(s.before, 'El ');
expect(s.after, ' come');
expect(s.trimmedBefore, isFalse);
Expand All @@ -29,16 +33,13 @@ void main() {
expect(s.after, isEmpty);
});

test(
'grapheme-based: a multi-code-unit char before the target keeps the '
'blank aligned',
() {
// The emoji is one grapheme but two UTF-16 code units; a code-unit
// split would land mid-character. Graphemes: ['🙂',' ','g','a','t','o'].
final s = GrammarErrorExampleWidget.splitAroundBlank('🙂 gato', 2, 4);
expect(s.before, '🙂 ');
expect(s.after, isEmpty);
},
);
test('grapheme-based: a multi-code-unit char before the target keeps the '
'blank aligned', () {
// The emoji is one grapheme but two UTF-16 code units; a code-unit
// split would land mid-character. Graphemes: ['🙂',' ','g','a','t','o'].
final s = GrammarErrorExampleWidget.splitAroundBlank('🙂 gato', 2, 4);
expect(s.before, '🙂 ');
expect(s.after, isEmpty);
});
});
}
25 changes: 14 additions & 11 deletions test/pangea/knock_space_response_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,20 @@ void main() {
});

group('BannedFromRoomException.fromErrorBody', () {
test('returns a typed exception with the banned list on the ban errcode', () {
final exception = BannedFromRoomException.fromErrorBody(
jsonEncode({
'errcode': 'ORG.PANGEA.BANNED_FROM_ROOM',
'error': 'You are banned from every matched room.',
'banned': ['!room:server'],
}),
);
expect(exception, isNotNull);
expect(exception!.banned, ['!room:server']);
});
test(
'returns a typed exception with the banned list on the ban errcode',
() {
final exception = BannedFromRoomException.fromErrorBody(
jsonEncode({
'errcode': 'ORG.PANGEA.BANNED_FROM_ROOM',
'error': 'You are banned from every matched room.',
'banned': ['!room:server'],
}),
);
expect(exception, isNotNull);
expect(exception!.banned, ['!room:server']);
},
);

test('tolerates a missing banned list', () {
final exception = BannedFromRoomException.fromErrorBody(
Expand Down
45 changes: 21 additions & 24 deletions test/pangea/orchestrator_controller_suggestion_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -113,30 +113,27 @@ void main() {
expect(result!.roleId, roleA);
});

test(
'lone human is prompted on the bot\'s opener, before their first '
'message (#7703)',
() {
// v2 re-fires on the bot's turn-0 opener (choreo#2761): the output is
// based on the bot's message and carries the human role's bucket (the
// sender's own role is omitted), so the lone human is prompted even
// though they have not spoken yet (latestHumanMessageSenderId null).
// The old currentUserSpokeLast gate dropped this — the moment
// forced-choice suggestions matter most for the PreA1-B1 audience.
const botOpener = r'$botOpener';
final output = outputWithBothRoles(botOpener);
final result = OrchestratorController.suggestionToShow(
output: output,
ownRoleId: roleA,
currentUserId: userA,
latestMessageEventId: botOpener,
latestHumanMessageSenderId: null, // no human has spoken yet
humanRoleCount: 1,
);
expect(result, isNotNull);
expect(result!.roleId, roleA);
},
);
test('lone human is prompted on the bot\'s opener, before their first '
'message (#7703)', () {
// v2 re-fires on the bot's turn-0 opener (choreo#2761): the output is
// based on the bot's message and carries the human role's bucket (the
// sender's own role is omitted), so the lone human is prompted even
// though they have not spoken yet (latestHumanMessageSenderId null).
// The old currentUserSpokeLast gate dropped this — the moment
// forced-choice suggestions matter most for the PreA1-B1 audience.
const botOpener = r'$botOpener';
final output = outputWithBothRoles(botOpener);
final result = OrchestratorController.suggestionToShow(
output: output,
ownRoleId: roleA,
currentUserId: userA,
latestMessageEventId: botOpener,
latestHumanMessageSenderId: null, // no human has spoken yet
humanRoleCount: 1,
);
expect(result, isNotNull);
expect(result!.roleId, roleA);
});
},
);

Expand Down
Loading