Repository: zulip/zulip-flutter (language: dart)

Issue:
Rely on Zulip Server 6 APIs
Since #1521, the app refuses to connect to servers older than Zulip Server 7. That means we can simplify away all the spots in our codebase where we have logic to accommodate older versions of the Zulip server.

The previous issue #268 was for removing the code for supporting Zulip Server 4, relying on server 5, as marked by comments `TODO(server-5)`. This one is for relying on server 6, as marked by comments `TODO(server-6)`. For details, see #268 and the previous issues linked from there.

### References
* Previous round of the same thing: #268 
* PR that implemented the previous round: #904 
* PR that disallowed connecting to older servers (a prerequisite for this issue): #1521 



Discussion hints:
nan

Pull request: Rely on Zulip Server 6 APIs
Fixes #1837.

This isn't our most urgent issue, but I was looking for some low-hanging fruit I could clear during yesterday's heat wave when it was really hot and uncomfortable at home. :)

Test command: flutter test test/model/channel_test.dart test/model/message_list_test.dart test/model/unreads_test.dart test/widgets/action_sheet_test.dart --reporter expanded 2>&1

Fix patch:
diff --git a/lib/api/core.dart b/lib/api/core.dart
index b4d05b5b34..7ac3f1f852 100644
--- a/lib/api/core.dart
+++ b/lib/api/core.dart
@@ -14,7 +14,7 @@ import 'exception.dart';
 ///
 /// When updating this, also update [kMinSupportedZulipFeatureLevel]
 /// and the README.
-// TODO(#992) address all TODO(server-6) and TODO(server-7)
+// TODO(#1838) address all TODO(server-7)
 const kMinSupportedZulipVersion = '7.0';
 
 /// The Zulip feature level reserved for the [kMinSupportedZulipVersion] release.
diff --git a/lib/api/model/events.dart b/lib/api/model/events.dart
index 5d86935186..9fd83c88f4 100644
--- a/lib/api/model/events.dart
+++ b/lib/api/model/events.dart
@@ -71,7 +71,6 @@ sealed class Event {
           case 'peer_remove': return SubscriptionPeerRemoveEvent.fromJson(json);
           default: return UnexpectedEvent.fromJson(json);
         }
-      // case 'muted_topics': … // TODO(#422) we ignore this feature on older servers
       case 'user_status': return UserStatusEvent.fromJson(json);
       case 'user_topic': return UserTopicEvent.fromJson(json);
       case 'muted_users': return MutedUsersEvent.fromJson(json);
@@ -763,6 +762,7 @@ class SubscriptionUpdateEvent extends SubscriptionEvent {
 
   final int streamId;
 
+  @JsonKey(unknownEnumValue: SubscriptionProperty.unknown)
   final SubscriptionProperty property;
 
   /// The new value, or null if we don't recognize the setting.
@@ -783,7 +783,6 @@ class SubscriptionUpdateEvent extends SubscriptionEvent {
         assert(RegExp(r'^#[0-9a-f]{6}$').hasMatch(str));
         return 0xff000000 | int.parse(str.substring(1), radix: 16);
       case SubscriptionProperty.isMuted:
-      case SubscriptionProperty.inHomeView:
       case SubscriptionProperty.pinToTop:
       case SubscriptionProperty.desktopNotifications:
       case SubscriptionProperty.audibleNotifications:
@@ -820,13 +819,18 @@ enum SubscriptionProperty {
   color,
 
   isMuted,
-  inHomeView,
   pinToTop,
   desktopNotifications,
   audibleNotifications,
   pushNotifications,
   emailNotifications,
   wildcardMentionsNotify,
+
+  /// A new, unrecognized property, or a deprecated one we don't use.
+  ///
+  /// Could be `in_home_view`, deprecated in FL 139 (Server 6) but still sent
+  /// as of CZO on 2025-10-03.
+  // TODO(server-future) Remove `in_home_view` comment once it stops being sent.
   unknown;
 
   static SubscriptionProperty fromRawString(String raw) => _byRawString[raw] ?? unknown;
diff --git a/lib/api/model/events.g.dart b/lib/api/model/events.g.dart
index 0313976018..7bf9ef0fcd 100644
--- a/lib/api/model/events.g.dart
+++ b/lib/api/model/events.g.dart
@@ -513,7 +513,11 @@ SubscriptionUpdateEvent _$SubscriptionUpdateEventFromJson(
 ) => SubscriptionUpdateEvent(
   id: (json['id'] as num).toInt(),
   streamId: (json['stream_id'] as num).toInt(),
-  property: $enumDecode(_$SubscriptionPropertyEnumMap, json['property']),
+  property: $enumDecode(
+    _$SubscriptionPropertyEnumMap,
+    json['property'],
+    unknownValue: SubscriptionProperty.unknown,
+  ),
   value: SubscriptionUpdateEvent._readValue(json, 'value'),
 );
 
@@ -531,7 +535,6 @@ Map<String, dynamic> _$SubscriptionUpdateEventToJson(
 const _$SubscriptionPropertyEnumMap = {
   SubscriptionProperty.color: 'color',
   SubscriptionProperty.isMuted: 'is_muted',
-  SubscriptionProperty.inHomeView: 'in_home_view',
   SubscriptionProperty.pinToTop: 'pin_to_top',
   SubscriptionProperty.desktopNotifications: 'desktop_notifications',
   SubscriptionProperty.audibleNotifications: 'audible_notifications',
diff --git a/lib/api/model/initial_snapshot.dart b/lib/api/model/initial_snapshot.dart
index 9250f75fa2..83e53fa1ca 100644
--- a/lib/api/model/initial_snapshot.dart
+++ b/lib/api/model/initial_snapshot.dart
@@ -35,8 +35,6 @@ class InitialSnapshot {
   @JsonKey(defaultValue: 10000)
   final int serverTypingStartedWaitPeriodMilliseconds;
 
-  // final List<…> mutedTopics; // TODO(#422) we ignore this feature on older servers
-
   final List<MutedUserItem> mutedUsers;
 
   // In the modern format because we pass `slim_presence`.
@@ -70,7 +68,7 @@ class InitialSnapshot {
 
   final UserSettings userSettings;
 
-  final List<UserTopicItem>? userTopics; // TODO(server-6)
+  final List<UserTopicItem> userTopics;
 
   final GroupSettingValue? realmCanDeleteAnyMessageGroup; // TODO(server-10)
 
@@ -111,7 +109,7 @@ class InitialSnapshot {
 
   final int maxFileUploadSizeMib;
 
-  final Uri? serverEmojiDataUrl; // TODO(server-6)
+  final Uri serverEmojiDataUrl;
 
   final String? realmEmptyTopicDisplayName; // TODO(server-10)
 
@@ -284,7 +282,7 @@ class UserSettings {
   )
   TwentyFourHourTimeMode twentyFourHourTime;
 
-  bool? displayEmojiReactionUsers; // TODO(server-6)
+  bool displayEmojiReactionUsers;
   Emojiset emojiset;
   bool presenceEnabled;
 
diff --git a/lib/api/model/initial_snapshot.g.dart b/lib/api/model/initial_snapshot.g.dart
index ffcbfba827..c2fd97dde5 100644
--- a/lib/api/model/initial_snapshot.g.dart
+++ b/lib/api/model/initial_snapshot.g.dart
@@ -78,8 +78,8 @@ InitialSnapshot _$InitialSnapshotFromJson(
   userSettings: UserSettings.fromJson(
     json['user_settings'] as Map<String, dynamic>,
   ),
-  userTopics: (json['user_topics'] as List<dynamic>?)
-      ?.map((e) => UserTopicItem.fromJson(e as Map<String, dynamic>))
+  userTopics: (json['user_topics'] as List<dynamic>)
+      .map((e) => UserTopicItem.fromJson(e as Map<String, dynamic>))
       .toList(),
   realmCanDeleteAnyMessageGroup:
       json['realm_can_delete_any_message_group'] == null
@@ -115,9 +115,7 @@ InitialSnapshot _$InitialSnapshotFromJson(
         ),
       ),
   maxFileUploadSizeMib: (json['max_file_upload_size_mib'] as num).toInt(),
-  serverEmojiDataUrl: json['server_emoji_data_url'] == null
-      ? null
-      : Uri.parse(json['server_emoji_data_url'] as String),
+  serverEmojiDataUrl: Uri.parse(json['server_emoji_data_url'] as String),
   realmEmptyTopicDisplayName: json['realm_empty_topic_display_name'] as String?,
   realmUsers:
       (InitialSnapshot._readUsersIsActiveFallbackTrue(json, 'realm_users')
@@ -186,7 +184,7 @@ Map<String, dynamic> _$InitialSnapshotToJson(
   'realm_presence_disabled': instance.realmPresenceDisabled,
   'realm_default_external_accounts': instance.realmDefaultExternalAccounts,
   'max_file_upload_size_mib': instance.maxFileUploadSizeMib,
-  'server_emoji_data_url': instance.serverEmojiDataUrl?.toString(),
+  'server_emoji_data_url': instance.serverEmojiDataUrl.toString(),
   'realm_empty_topic_display_name': instance.realmEmptyTopicDisplayName,
   'realm_users': instance.realmUsers,
   'realm_non_active_users': instance.realmNonActiveUsers,
@@ -248,7 +246,7 @@ UserSettings _$UserSettingsFromJson(Map<String, dynamic> json) => UserSettings(
   twentyFourHourTime: TwentyFourHourTimeMode.fromApiValue(
     json['twenty_four_hour_time'] as bool?,
   ),
-  displayEmojiReactionUsers: json['display_emoji_reaction_users'] as bool?,
+  displayEmojiReactionUsers: json['display_emoji_reaction_users'] as bool,
   emojiset: $enumDecode(_$EmojisetEnumMap, json['emojiset']),
   presenceEnabled: json['presence_enabled'] as bool,
 );
diff --git a/lib/api/model/model.dart b/lib/api/model/model.dart
index 2bc6d7d487..fdfd0c856d 100644
--- a/lib/api/model/model.dart
+++ b/lib/api/model/model.dart
@@ -69,7 +69,7 @@ class CustomProfileField {
   final String name;
   final String hint;
   final String fieldData;
-  final bool? displayInProfileSummary; // TODO(server-6)
+  final bool? displayInProfileSummary;
 
   CustomProfileField({
     required this.id,
@@ -97,7 +97,7 @@ enum CustomProfileFieldType {
   link(apiValue: 5),
   user(apiValue: 6),
   externalAccount(apiValue: 7),
-  pronouns(apiValue: 8), // TODO(server-6) newly added
+  pronouns(apiValue: 8),
   unknown(apiValue: null);
 
   const CustomProfileFieldType({
@@ -257,7 +257,6 @@ class StatusEmoji {
 ///
 /// The absence of one of these means there is no change.
 class UserStatusChange {
-  // final Option<bool> away; // deprecated in server-6 (FL-148); ignore
   final Option<String?> text;
   final Option<StatusEmoji?> emoji;
 
@@ -793,7 +792,6 @@ class Subscription extends ZulipStream {
 
   bool pinToTop;
   bool isMuted;
-  // final bool? inHomeView; // deprecated; ignore
 
   /// As an int that dart:ui's Color constructor will take:
   ///   <https://api.flutter.dev/flutter/dart-ui/Color/Color.html>
diff --git a/lib/model/channel.dart b/lib/model/channel.dart
index 098f146df1..ea05dc01d3 100644
--- a/lib/model/channel.dart
+++ b/lib/model/channel.dart
@@ -307,7 +307,7 @@ class ChannelStoreImpl extends HasUserStore with ChannelStore {
     }
 
     final topicVisibility = <int, TopicKeyedMap<UserTopicVisibilityPolicy>>{};
-    for (final item in initialSnapshot.userTopics ?? const <UserTopicItem>[]) {
+    for (final item in initialSnapshot.userTopics) {
       if (_warnInvalidVisibilityPolicy(item.visibilityPolicy)) {
         // Not a value we expect. Keep it out of our data structures. // TODO(log)
         continue;
@@ -473,8 +473,6 @@ class ChannelStoreImpl extends HasUserStore with ChannelStore {
           case SubscriptionProperty.isMuted:
             // TODO(#1255) update [MessageListView] if affected
             subscription.isMuted                = event.value as bool;
-          case SubscriptionProperty.inHomeView:
-            subscription.isMuted                = !(event.value as bool);
           case SubscriptionProperty.pinToTop:
             subscription.pinToTop               = event.value as bool;
           case SubscriptionProperty.desktopNotifications:
diff --git a/lib/model/message_list.dart b/lib/model/message_list.dart
index fcb6943905..91868df2b2 100644
--- a/lib/model/message_list.dart
+++ b/lib/model/message_list.dart
@@ -873,12 +873,6 @@ class MessageListView with ChangeNotifier, _MessageSequence {
       numBefore: kMessageListFetchBatchSize,
       numAfter: 0,
       processResult: (result) {
-        if (result.messages.isNotEmpty
-            && result.messages.last.id == messages[0].id) {
-          // TODO(server-6): includeAnchor should make this impossible
-          result.messages.removeLast();
-        }
-
         store.reconcileMessages(result.messages);
         store.recentSenders.handleMessages(result.messages); // TODO(#824)
 
@@ -909,12 +903,6 @@ class MessageListView with ChangeNotifier, _MessageSequence {
       numBefore: 0,
       numAfter: kMessageListFetchBatchSize,
       processResult: (result) {
-        if (result.messages.isNotEmpty
-            && result.messages.first.id == messages.last.id) {
-          // TODO(server-6): includeAnchor should make this impossible
-          result.messages.removeAt(0);
-        }
-
         store.reconcileMessages(result.messages);
         store.recentSenders.handleMessages(result.messages); // TODO(#824)
 
diff --git a/lib/model/store.dart b/lib/model/store.dart
index fe503da36e..f39757343d 100644
--- a/lib/model/store.dart
+++ b/lib/model/store.dart
@@ -1117,12 +1117,7 @@ class UpdateMachine {
     final updateMachine = UpdateMachine.fromInitialSnapshot(
       store: store, initialSnapshot: initialSnapshot);
     updateMachine.poll();
-    if (initialSnapshot.serverEmojiDataUrl != null) {
-      // TODO(server-6): If the server is ancient, just skip trying to have
-      //   a list of its emoji.  (The old servers that don't provide
-      //   serverEmojiDataUrl are already unsupported at time of writing.)
-      unawaited(updateMachine.fetchEmojiData(initialSnapshot.serverEmojiDataUrl!));
-    }
+    unawaited(updateMachine.fetchEmojiData(initialSnapshot.serverEmojiDataUrl));
     store.presence.start();
     return updateMachine;
   }
diff --git a/lib/model/unreads.dart b/lib/model/unreads.dart
index 616feb18ef..543cde28b6 100644
--- a/lib/model/unreads.dart
+++ b/lib/model/unreads.dart
@@ -453,12 +453,8 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
             final newlyUnreadInDms = <DmNarrow, QueueList<int>>{};
             for (final messageId in event.messages) {
               final detail = event.messageDetails![messageId];
-              if (detail == null) { // TODO(log) if on Zulip 6.0+
-                // Happens as a bug in some cases before fixed in Zulip 6.0:
-                //   https://chat.zulip.org/#narrow/stream/378-api-design/topic/unreads.20in.20unsubscribed.20streams/near/1458467
-                // TODO(server-6) remove Zulip 6.0 comment
-                continue;
-              }
+              if (detail == null) continue; // TODO(log)
+
               if (detail.mentioned == true) {
                 mentions.add(messageId);
               }
@@ -537,11 +533,7 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
     final topics = streams[streamId] ??= makeTopicKeyedMap();
     topics.update(topic,
       ifAbsent: () => messageIds,
-      // setUnion dedupes existing and incoming unread IDs,
-      // so we tolerate zulip/zulip#22164, fixed in 6.0
-      // TODO(server-6) remove 6.0 comment
-      (existing) => setUnion(existing, messageIds),
-    );
+      (existing) => setUnion(existing, messageIds));
   }
 
   /// Remove [idsToRemove] from [streams] and [dms].
@@ -640,10 +632,6 @@ class Unreads extends PerAccountStoreBase with ChangeNotifier {
   void _addAllInDm(QueueList<int> messageIds, DmNarrow dmNarrow) {
     dms.update(dmNarrow,
       ifAbsent: () => messageIds,
-      // setUnion dedupes existing and incoming unread IDs,
-      // so we tolerate zulip/zulip#22164, fixed in 6.0
-      // TODO(server-6) remove 6.0 comment
-      (existing) => setUnion(existing, messageIds),
-    );
+      (existing) => setUnion(existing, messageIds));
   }
 }
diff --git a/lib/widgets/action_sheet.dart b/lib/widgets/action_sheet.dart
index c01b8b9937..1d41a574d5 100644
--- a/lib/widgets/action_sheet.dart
+++ b/lib/widgets/action_sheet.dart
@@ -1013,8 +1013,6 @@ void showMessageActionSheet({required BuildContext context, required Message mes
   final isComposeBoxOffered = messageListPage.composeBoxState != null;
 
   final isMessageRead = message.flags.contains(MessageFlag.read);
-  final markAsUnreadSupported = store.zulipFeatureLevel >= 155; // TODO(server-6)
-  final showMarkAsUnreadButton = markAsUnreadSupported && isMessageRead;
 
   final isSenderMuted = store.isUserMuted(message.senderId);
 
@@ -1029,7 +1027,7 @@ void showMessageActionSheet({required BuildContext context, required Message mes
       StarButton(message: message, pageContext: pageContext),
       if (isComposeBoxOffered)
         QuoteAndReplyButton(message: message, pageContext: pageContext),
-      if (showMarkAsUnreadButton)
+      if (isMessageRead)
         MarkAsUnreadButton(message: message, pageContext: pageContext),
       if (isSenderMuted)
         // The message must have been revealed in order to open this action sheet.
@@ -1109,10 +1107,7 @@ bool _getShouldShowEditButton(BuildContext pageContext, Message message) {
 
   final now = ZulipBinding.instance.utcNow().millisecondsSinceEpoch ~/ 1000;
   final editLimit = store.realmMessageContentEditLimitSeconds;
-  final outsideEditLimit =
-    editLimit != null
-    && editLimit != 0 // TODO(server-6) remove (pre-FL 138, 0 represents no limit)
-    && now - message.timestamp > editLimit;
+  final outsideEditLimit = editLimit != null && now - message.timestamp > editLimit;
 
   return message.senderId == store.selfUserId
     && isComposeBoxOffered
diff --git a/lib/widgets/actions.dart b/lib/widgets/actions.dart
index dd34564768..b75a85ecef 100644
--- a/lib/widgets/actions.dart
+++ b/lib/widgets/actions.dart
@@ -68,7 +68,6 @@ abstract final class ZulipAction {
     Message message,
     Narrow narrow,
   ) async {
-    assert(PerAccountStoreWidget.of(context).zulipFeatureLevel >= 155); // TODO(server-6)
     final zulipLocalizations = ZulipLocalizations.of(context);
     await updateMessageFlagsStartingFromAnchor(
       context: context,
diff --git a/lib/widgets/emoji_reaction.dart b/lib/widgets/emoji_reaction.dart
index b3117e465e..2c7c5d61ab 100644
--- a/lib/widgets/emoji_reaction.dart
+++ b/lib/widgets/emoji_reaction.dart
@@ -130,7 +130,7 @@ class ReactionChipsList extends StatelessWidget {
   Widget build(BuildContext context) {
     final zulipLocalizations = ZulipLocalizations.of(context);
     final store = PerAccountStoreWidget.of(context);
-    final displayEmojiReactionUsers = store.userSettings.displayEmojiReactionUsers ?? false;
+    final displayEmojiReactionUsers = store.userSettings.displayEmojiReactionUsers;
     final showNames = displayEmojiReactionUsers && reactions.total <= 3;
 
     Widget result = Wrap(spacing: 4, runSpacing: 4, crossAxisAlignment: WrapCrossAlignment.center,


Test patch:
diff --git a/test/example_data.dart b/test/example_data.dart
index 9bc82b9d6d..a26b94ff13 100644
--- a/test/example_data.dart
+++ b/test/example_data.dart
@@ -821,14 +821,13 @@ GetMessagesResult nearGetMessagesResult({
 /// A GetMessagesResult the server might return when we request older messages.
 GetMessagesResult olderGetMessagesResult({
   required int anchor,
-  bool foundAnchor = false, // the value if the server understood includeAnchor false
   required bool foundOldest,
   bool historyLimited = false,
   required List<Message> messages,
 }) {
   return GetMessagesResult(
     anchor: anchor,
-    foundAnchor: foundAnchor,
+    foundAnchor: false,
     foundNewest: false, // empirically always this, even when anchor happens to be latest
     foundOldest: foundOldest,
     historyLimited: historyLimited,
@@ -839,14 +838,13 @@ GetMessagesResult olderGetMessagesResult({
 /// A GetMessagesResult the server might return when we request newer messages.
 GetMessagesResult newerGetMessagesResult({
   required int anchor,
-  bool foundAnchor = false, // the value if the server understood includeAnchor false
   required bool foundNewest,
   bool historyLimited = false,
   required List<Message> messages,
 }) {
   return GetMessagesResult(
     anchor: anchor,
-    foundAnchor: foundAnchor,
+    foundAnchor: false,
     foundOldest: false,
     foundNewest: foundNewest,
     historyLimited: historyLimited,
@@ -1334,7 +1332,7 @@ InitialSnapshot initialSnapshot({
       emojiset: Emojiset.google,
       presenceEnabled: true,
     ),
-    userTopics: userTopics,
+    userTopics: userTopics ?? [],
     // no default; allow `null` to simulate servers without this
     realmCanDeleteAnyMessageGroup: realmCanDeleteAnyMessageGroup,
     realmCanDeleteOwnMessageGroup: realmCanDeleteOwnMessageGroup,
diff --git a/test/model/channel_test.dart b/test/model/channel_test.dart
index e7e57357d6..e73965dac2 100644
--- a/test/model/channel_test.dart
+++ b/test/model/channel_test.dart
@@ -121,20 +121,6 @@ void main() {
         value: true));
       check(store.subscriptions[stream.streamId]!.isMuted).isTrue();
     });
-
-    test('SubscriptionProperty.inHomeView updates isMuted instead', () async {
-      final store = eg.store(initialSnapshot: eg.initialSnapshot(
-        streams: [stream],
-        subscriptions: [eg.subscription(stream, isMuted: false)],
-      ));
-      check(store.subscriptions[stream.streamId]!.isMuted).isFalse();
-
-      await store.handleEvent(SubscriptionUpdateEvent(id: 1,
-        streamId: stream.streamId,
-        property: SubscriptionProperty.inHomeView,
-        value: false));
-      check(store.subscriptions[stream.streamId]!.isMuted).isTrue();
-    });
   });
 
   group('topic visibility', () {
diff --git a/test/model/message_list_test.dart b/test/model/message_list_test.dart
index 9cb4977a32..39f4305ff3 100644
--- a/test/model/message_list_test.dart
+++ b/test/model/message_list_test.dart
@@ -625,40 +625,6 @@ void main() {
       check(connection.takeRequests()).single;
     }));
 
-    test('fetchOlder handles servers not understanding includeAnchor', () async {
-      await prepare();
-      await prepareMessages(foundOldest: false,
-        messages: List.generate(100, (i) => eg.streamMessage(id: 1000 + i)));
-
-      // The old behavior is to include the anchor message regardless of includeAnchor.
-      connection.prepare(json: olderResult(
-        anchor: 1000, foundOldest: false, foundAnchor: true,
-        messages: List.generate(101, (i) => eg.streamMessage(id: 900 + i)),
-      ).toJson());
-      await model.fetchOlder();
-      checkNotified(count: 2);
-      check(model)
-        ..busyFetchingMore.isFalse()
-        ..messages.length.equals(200);
-    });
-
-    test('fetchNewer handles servers not understanding includeAnchor', () async {
-      await prepare(anchor: NumericAnchor(1000));
-      await prepareMessages(foundOldest: true, foundNewest: false,
-        messages: List.generate(101, (i) => eg.streamMessage(id: 1000 + i)));
-
-      // The old behavior is to include the anchor message regardless of includeAnchor.
-      connection.prepare(json: newerResult(
-        anchor: 1100, foundNewest: false, foundAnchor: true,
-        messages: List.generate(101, (i) => eg.streamMessage(id: 1100 + i)),
-      ).toJson());
-      await model.fetchNewer();
-      checkNotified(count: 2);
-      check(model)
-        ..busyFetchingMore.isFalse()
-        ..messages.length.equals(201);
-    });
-
     // TODO(#824): move this test
     test('fetchOlder recent senders track all the messages', () async {
       await prepare();
diff --git a/test/model/unreads_test.dart b/test/model/unreads_test.dart
index 4952354044..089ef315cb 100644
--- a/test/model/unreads_test.dart
+++ b/test/model/unreads_test.dart
@@ -1250,22 +1250,6 @@ void main() {
         checkMatchesMessages([message1, message2]);
       });
 
-      // TODO(server-6) remove mention of zulip/zulip#22164, fixed in 6.0
-      test('tolerates event pointing to DM/stream messages that are already unread (zulip/zulip#22164)', () {
-        final message1 = eg.streamMessage(id: 1, flags: []);
-        final message2 = eg.dmMessage(id: 2, from: eg.otherUser, to: [eg.selfUser], flags: []);
-
-        prepare();
-        fillWithMessages([message1, message2]);
-
-        model.handleUpdateMessageFlagsEvent(mkEvent([message1, message2]));
-        checkNotifiedOnce();
-
-        message1.flags.remove(MessageFlag.read);
-        message2.flags.remove(MessageFlag.read);
-        checkMatchesMessages([message1, message2]);
-      });
-
       test('tolerates "message details" missing', () {
         final stream = eg.stream();
         const topic = 'a';
diff --git a/test/widgets/action_sheet_test.dart b/test/widgets/action_sheet_test.dart
index 22a4b06d8e..06e0eab5ca 100644
--- a/test/widgets/action_sheet_test.dart
+++ b/test/widgets/action_sheet_test.dart
@@ -2202,8 +2202,6 @@ void main() {
         }
 
         testVisibility(true);
-        // TODO(server-6) limit 0 not expected on 6.0+
-        testVisibility(true, limit: 0);
         testVisibility(true, limit: 600);
         testVisibility(true, narrow: ChannelNarrow(1));