···1818 /// Calls com.atproto.repo.describeRepo to get available collections
1919 /// for the specified DID.
2020 ///
2121- /// Returns a list of collections with their NSIDs and record counts.
2121+ /// Returns a list of collections with their NSIDs. Note that the API
2222+ /// only returns collection NSIDs (as strings), not record counts.
2223 Future<List<RepoCollection>> describeRepo(String did) async {
2324 _logger.info('Describing repo for DID: $did');
2425···3132 return [];
3233 }
33343434- return collections
3535- .map((json) => RepoCollection.fromJson(json as Map<String, dynamic>))
3636- .toList();
3535+ return collections.map((nsid) => RepoCollection.fromNsid(nsid as String)).toList();
3736 } catch (e, stack) {
3837 _logger.error('Failed to describe repo for DID: $did', e, stack);
3938 rethrow;
···1010///
1111/// This client is used for unauthenticated reads like fetching profiles, threads, and
1212/// search results.
1313+///
1414+/// The [listFormat] is set to [ListFormat.multi] to serialize array query
1515+/// parameters as repeated parameter names (e.g., ?feeds=uri1&feeds=uri2), which is
1616+/// the format expected by AT Protocol endpoints.
1317Dio createPublicDio({bool enableLogging = true, List<Interceptor> interceptors = const []}) {
1418 final dio = Dio(
1519 BaseOptions(
···1822 receiveTimeout: const Duration(seconds: 30),
1923 sendTimeout: const Duration(seconds: 30),
2024 headers: {'Accept': 'application/json', 'Content-Type': 'application/json'},
2525+ listFormat: ListFormat.multi,
2126 ),
2227 );
2328···3439///
3540/// This client is used for authenticated operations and requires the PDS URL to be provided
3641/// (i.e. resolved from user's DID document).
4242+///
4343+/// The [listFormat] is set to [ListFormat.multi] to serialize array query
4444+/// parameters as repeated parameter names (e.g., ?feeds=uri1&feeds=uri2), which is
4545+/// the format expected by AT Protocol endpoints.
3746Dio createPdsDio({
3847 required String pdsUrl,
3948 required SessionGetter getSession,
···5059 receiveTimeout: const Duration(seconds: 30),
5160 sendTimeout: const Duration(seconds: 30),
5261 headers: {'Accept': 'application/json', 'Content-Type': 'application/json'},
6262+ listFormat: ListFormat.multi,
5363 ),
5464 );
5565
···229229 expect(meta.proxyKind, equals(ProxyKind.chat), reason: '$nsid should use chat proxy');
230230 }
231231 });
232232+233233+ test('feed generator endpoints are registered', () {
234234+ final feedEndpoints = ['app.bsky.feed.getFeedGenerator', 'app.bsky.feed.getFeedGenerators'];
235235+236236+ for (final nsid in feedEndpoints) {
237237+ final meta = registry.get(nsid);
238238+ expect(meta.hostKind, equals(HostKind.publicApi), reason: '$nsid should use publicApi');
239239+ expect(meta.requiresAuth, isFalse, reason: '$nsid should not require auth');
240240+ expect(meta.method, equals(HttpMethod.get), reason: '$nsid should be GET');
241241+ }
242242+ });
243243+244244+ test('graph list endpoints are registered', () {
245245+ final listEndpoints = ['app.bsky.graph.getList'];
246246+247247+ for (final nsid in listEndpoints) {
248248+ final meta = registry.get(nsid);
249249+ expect(meta.hostKind, equals(HostKind.publicApi), reason: '$nsid should use publicApi');
250250+ expect(meta.requiresAuth, isFalse, reason: '$nsid should not require auth');
251251+ expect(meta.method, equals(HttpMethod.get), reason: '$nsid should be GET');
252252+ }
253253+ });
232254 });
233255}