Skip to content
Open
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
3 changes: 2 additions & 1 deletion desktop/scripts/check-file-sizes.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,8 @@ const overrides = new Map([
// databricks-v1-to-v2-migration: databricks-v2 hyphen-alias added to all
// host/credential match arms + 30+ readiness tests for provider aliases,
// missing-host, and DATABRICKS_MODEL fallback. Load-bearing correctness fix.
["src-tauri/src/managed_agents/readiness.rs", 1546],
// +3 lines landed on main after the last ratchet; preserve current headroom.
["src-tauri/src/managed_agents/readiness.rs", 1549],
// applyWorkspace reposDir parameter plus the validateReposDir binding,
// threaded through Tauri invokes for configurable repos_dir, plus the
// harness-persona-sync `harnessOverride` create-input bit — load-bearing
Expand Down
1 change: 1 addition & 0 deletions mobile/lib/features/channels/channel_detail_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import '../profile/user_cache_provider.dart';
import '../profile/user_profile.dart';
import '../forum/forum_posts_view.dart';
import 'channel.dart';
import 'channel_link_navigation.dart';
import 'agent_activity/working_bots_provider.dart';
import 'channel_management_provider.dart';
import 'channel_messages_provider.dart';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,24 +115,11 @@ class _MessageBubble extends ConsumerWidget {
channelNames: channelNames,
tags: message.tags,
onChannelTap: (channelId) {
if (channelId == currentChannelId) return;
final channelsAsync = ref.read(channelsProvider);
final channels = channelsAsync.hasValue
? channelsAsync.value
: null;
Channel? targetChannel;
for (final channel in channels ?? const <Channel>[]) {
if (channel.id == channelId) {
targetChannel = channel;
break;
}
}
if (targetChannel == null) return;
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) =>
ChannelDetailPage(channel: targetChannel!),
),
openChannelLink(
context: context,
ref: ref,
channelId: channelId,
currentChannelId: currentChannelId,
);
},
),
Expand Down
38 changes: 38 additions & 0 deletions mobile/lib/features/channels/channel_link_navigation.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';

import 'channel.dart';
import 'channel_detail_page.dart';
import 'channels_provider.dart';

void openChannelLink({
required BuildContext context,
required WidgetRef ref,
required String channelId,
required String currentChannelId,
}) {
if (channelId == currentChannelId) return;

final channelsAsync = ref.read(channelsProvider);
final channels = channelsAsync.hasValue ? channelsAsync.value : null;
Channel? targetChannel;
for (final channel in channels ?? const <Channel>[]) {
if (channel.id == channelId) {
targetChannel = channel;
break;
}
}

if (targetChannel == null) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Channel could not be opened')),
);
return;
}

Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (_) => ChannelDetailPage(channel: targetChannel!),
),
);
}
10 changes: 9 additions & 1 deletion mobile/lib/features/channels/thread_detail_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import '../../shared/widgets/frosted_app_bar.dart';
import '../../shared/widgets/frosted_scaffold.dart';
import '../profile/user_cache_provider.dart';
import '../profile/user_profile.dart';
import 'channel_link_navigation.dart';
import 'channel_typing_provider.dart';
import 'thread_replies_provider.dart';
import 'channels_provider.dart';
Expand Down Expand Up @@ -468,7 +469,14 @@ class _ThreadMessage extends ConsumerWidget {
mentionNames: mentionNames,
channelNames: channelNames,
tags: message.tags,
onChannelTap: (_) {},
onChannelTap: (targetChannelId) {
openChannelLink(
context: context,
ref: ref,
channelId: targetChannelId,
currentChannelId: channelId,
);
},
),
if (message.reactions.isNotEmpty)
ReactionRow(
Expand Down
112 changes: 101 additions & 11 deletions mobile/test/features/channels/channel_detail_page_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import 'package:buzz/features/channels/channel_detail_page.dart';
import 'package:buzz/features/channels/channel_management_provider.dart';
import 'package:buzz/features/channels/channel_messages_provider.dart';
import 'package:buzz/features/channels/channel_typing_provider.dart';
import 'package:buzz/features/channels/thread_detail_page.dart';
import 'package:buzz/features/channels/timeline_message.dart';
import 'package:buzz/features/channels/channels_provider.dart';
import 'package:buzz/features/channels/read_state/read_state_provider.dart';
import 'package:buzz/features/profile/profile_provider.dart';
Expand Down Expand Up @@ -1099,17 +1101,7 @@ void main() {

group('Channel links', () {
testWidgets('tapping a channel link opens that channel', (tester) async {
final randomChannel = Channel(
id: 'random-channel',
name: 'random',
channelType: 'stream',
visibility: 'open',
description: 'Random discussion',
createdBy: 'abc123',
createdAt: DateTime(2025),
memberCount: 3,
isMember: true,
);
final randomChannel = _channel(id: 'random-channel', name: 'random');
final observer = _TestNavigatorObserver();

await tester.pumpWidget(
Expand Down Expand Up @@ -1137,9 +1129,107 @@ void main() {

expect(observer.pushCount, initialPushCount + 1);
});

testWidgets('missing channel link shows an error', (tester) async {
final randomChannel = _channel(id: 'random-channel', name: 'random');
final channelsNotifier = _FakeChannelsNotifier([
_testChannel,
randomChannel,
]);

await tester.pumpWidget(
_buildTestable(
messages: [
_textMsg(
id: 'msg1',
pubkey: 'alice',
content: 'Take this to #random',
createdAt: 1000,
),
],
users: {
'alice': const UserProfile(pubkey: 'alice', displayName: 'Alice'),
},
channelsNotifier: channelsNotifier,
),
);
await tester.pumpAndSettle();

channelsNotifier.setChannels([_testChannel]);
await tester.tap(find.text('#random'));
await tester.pump();

expect(find.text('Channel could not be opened'), findsOneWidget);
});

testWidgets('tapping a channel link inside a thread opens that channel', (
tester,
) async {
final randomChannel = _channel(id: 'random-channel', name: 'random');
final observer = _TestNavigatorObserver();

await tester.pumpWidget(
_buildTestable(
messages: [
_textMsg(
id: 'msg1',
pubkey: 'alice',
content: 'Thread root #random',
createdAt: 1000,
),
],
users: {
'alice': const UserProfile(pubkey: 'alice', displayName: 'Alice'),
},
channels: [_testChannel, randomChannel],
navigatorObservers: [observer],
),
);
await tester.pumpAndSettle();

final threadMessages = formatTimeline([
_textMsg(
id: 'msg1',
pubkey: 'alice',
content: 'Thread root #random',
createdAt: 1000,
),
]);
Navigator.of(tester.element(find.byType(ChannelDetailPage))).push(
MaterialPageRoute<void>(
builder: (_) => ThreadDetailPage(
threadHead: threadMessages.single,
allMessages: threadMessages,
channelId: _channelId,
currentPubkey: 'self',
isMember: true,
isArchived: false,
),
),
);
await tester.pumpAndSettle();
final initialPushCount = observer.pushCount;

await tester.tap(find.text('#random').last);
await tester.pumpAndSettle();

expect(observer.pushCount, initialPushCount + 1);
});
});
}

Channel _channel({required String id, required String name}) => Channel(
id: id,
name: name,
channelType: 'stream',
visibility: 'open',
description: '$name discussion',
createdBy: 'abc123',
createdAt: DateTime(2025),
memberCount: 3,
isMember: true,
);

class _FakeMessagesNotifier extends ChannelMessagesNotifier {
List<NostrEvent> _messages;
_FakeMessagesNotifier(this._messages) : super(_channelId);
Expand Down
Loading