@brainfish-ai/devdoc 0.1.43 → 0.1.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/cli/commands/create.js +2 -2
  2. package/package.json +1 -1
  3. package/renderer/app/api/collections/route.js +35 -4
  4. package/renderer/app/api/suggestions/route.js +33 -13
  5. package/renderer/app/globals.css +69 -0
  6. package/renderer/app/layout.js +2 -2
  7. package/renderer/app/llms-full.txt/route.js +10 -1
  8. package/renderer/app/llms.txt/route.js +10 -1
  9. package/renderer/app/sitemap.xml/route.js +11 -1
  10. package/renderer/components/docs/mdx/cards.js +1 -1
  11. package/renderer/components/docs/mdx/landing.js +7 -5
  12. package/renderer/components/docs-viewer/agent/agent-chat.js +13 -112
  13. package/renderer/components/docs-viewer/agent/agent-popup-button.js +99 -0
  14. package/renderer/components/docs-viewer/agent/index.js +3 -0
  15. package/renderer/components/docs-viewer/content/content-router.js +182 -0
  16. package/renderer/components/docs-viewer/content/doc-page.js +31 -5
  17. package/renderer/components/docs-viewer/content/index.js +2 -0
  18. package/renderer/components/docs-viewer/index.js +381 -485
  19. package/renderer/components/docs-viewer/playground/graphql-playground.js +205 -3
  20. package/renderer/components/docs-viewer/sidebar/right-sidebar.js +35 -39
  21. package/renderer/components/theme-toggle.js +1 -21
  22. package/renderer/hooks/use-route-state.js +159 -0
  23. package/renderer/lib/api-docs/agent/use-suggestions.js +97 -0
  24. package/renderer/lib/api-docs/code-editor/mode-context.js +61 -89
  25. package/renderer/lib/api-docs/mobile-context.js +40 -3
  26. package/renderer/lib/docs/config/environment.js +38 -0
  27. package/renderer/lib/docs/config/index.js +1 -0
  28. package/renderer/lib/docs/config/schema.js +17 -5
  29. package/renderer/lib/docs/mdx/compiler.js +5 -2
  30. package/renderer/lib/docs/mdx/index.js +2 -0
  31. package/renderer/lib/docs/mdx/remark-mermaid.js +63 -0
  32. package/renderer/lib/docs/navigation/index.js +1 -2
  33. package/renderer/lib/docs/navigation/types.js +3 -1
  34. package/renderer/lib/docs-navigation.js +140 -0
  35. package/renderer/package.json +1 -0
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
- import { useEffect, useState, useCallback, useRef } from 'react';
3
+ import { useEffect, useState, useCallback, useRef, useMemo } from 'react';
4
4
  import { Spinner } from '@phosphor-icons/react';
5
5
  import { DocsSidebar } from './sidebar';
6
6
  import { NotFoundPage } from './content/not-found-page';
@@ -11,7 +11,7 @@ import { DocPage } from './content/doc-page';
11
11
  import { ChangelogPage } from './content/changelog-page';
12
12
  import { GraphQLPlayground } from './playground/graphql-playground';
13
13
  import { makeBrainfishCollection } from '@/lib/api-docs/factories';
14
- import { parse, Kind } from 'graphql';
14
+ import { buildSchema, isNonNullType, isListType } from 'graphql';
15
15
  import { GlobalAuthModal } from './global-auth-modal';
16
16
  import { AuthProvider } from '@/lib/api-docs/auth';
17
17
  import { PlaygroundProvider, usePlaygroundPrefill } from '@/lib/api-docs/playground/context';
@@ -27,57 +27,59 @@ import { Code, TestTube, Book } from '@phosphor-icons/react';
27
27
  import { cn } from '@/lib/utils';
28
28
  import { SearchDialog, useSearch } from './search';
29
29
  import { useTheme } from 'next-themes';
30
- // Helper to convert GraphQL TypeNode to string
31
- function typeNodeToString(typeNode) {
32
- switch(typeNode.kind){
33
- case Kind.NAMED_TYPE:
34
- return typeNode.name.value;
35
- case Kind.NON_NULL_TYPE:
36
- return `${typeNodeToString(typeNode.type)}!`;
37
- case Kind.LIST_TYPE:
38
- return `[${typeNodeToString(typeNode.type)}]`;
39
- default:
40
- return 'Unknown';
30
+ import { useRouteState } from '@/hooks/use-route-state';
31
+ import { navigateToPage, navigateToEndpoint, navigateToSection, navigateToTab } from '@/lib/docs-navigation';
32
+ import { useMobile } from '@/lib/api-docs/mobile-context';
33
+ import { AgentPopupButton } from './agent/agent-popup-button';
34
+ import { buildEndpointIndex } from '@/lib/api-docs/agent/indexer';
35
+ // Helper to convert GraphQL type to string representation
36
+ function graphqlTypeToString(type) {
37
+ if (isNonNullType(type)) {
38
+ return `${graphqlTypeToString(type.ofType)}!`;
41
39
  }
40
+ if (isListType(type)) {
41
+ return `[${graphqlTypeToString(type.ofType)}]`;
42
+ }
43
+ // Named type
44
+ return type.name || 'Unknown';
42
45
  }
43
- // Parse GraphQL schema using the graphql library
46
+ // Parse GraphQL schema using buildSchema (for SDL)
44
47
  function parseGraphQLSchema(schemaSDL) {
45
48
  const operations = [];
46
49
  try {
47
- const ast = parse(schemaSDL);
48
- // Find Query, Mutation, Subscription type definitions
49
- for (const def of ast.definitions){
50
- if (def.kind === Kind.OBJECT_TYPE_DEFINITION) {
51
- const typeName = def.name.value;
52
- // Only process root operation types
53
- if (![
54
- 'Query',
55
- 'Mutation',
56
- 'Subscription'
57
- ].includes(typeName)) continue;
58
- const operationType = typeName.toLowerCase();
59
- // Process each field
60
- for (const field of def.fields || []){
61
- const name = field.name.value;
62
- // Skip internal fields
63
- if (name.startsWith('_')) continue;
64
- // Get description
65
- const description = field.description?.value || null;
66
- // Get return type
67
- const returnType = typeNodeToString(field.type);
68
- // Build args string for query generation
69
- const args = (field.arguments || []).map((arg)=>`${arg.name.value}: ${typeNodeToString(arg.type)}`).join(', ');
70
- const query = generateGraphQLQuery(operationType, name, field.arguments || [], returnType);
71
- const exampleVariables = generateGraphQLVariables(field.arguments || []);
72
- operations.push({
73
- id: `${operationType}-${name}`,
74
- name,
75
- description,
76
- operationType,
77
- query,
78
- exampleVariables
79
- });
80
- }
50
+ const schema = buildSchema(schemaSDL);
51
+ // Get root operation types
52
+ const rootTypes = [
53
+ {
54
+ type: schema.getQueryType(),
55
+ operationType: 'query'
56
+ },
57
+ {
58
+ type: schema.getMutationType(),
59
+ operationType: 'mutation'
60
+ },
61
+ {
62
+ type: schema.getSubscriptionType(),
63
+ operationType: 'subscription'
64
+ }
65
+ ];
66
+ for (const { type: rootType, operationType } of rootTypes){
67
+ if (!rootType) continue;
68
+ const fields = rootType.getFields();
69
+ for (const [fieldName, field] of Object.entries(fields)){
70
+ // Skip internal fields
71
+ if (fieldName.startsWith('_')) continue;
72
+ const returnType = graphqlTypeToString(field.type);
73
+ const query = generateGraphQLQueryFromField(operationType, fieldName, field.args, returnType, schema);
74
+ const exampleVariables = generateGraphQLVariablesFromArgs(field.args, schema);
75
+ operations.push({
76
+ id: `${operationType}-${fieldName}`,
77
+ name: fieldName,
78
+ description: field.description || null,
79
+ operationType,
80
+ query,
81
+ exampleVariables
82
+ });
81
83
  }
82
84
  }
83
85
  } catch (err) {
@@ -85,16 +87,59 @@ function parseGraphQLSchema(schemaSDL) {
85
87
  }
86
88
  return operations;
87
89
  }
88
- // Generate example GraphQL query from AST
89
- function generateGraphQLQuery(operationType, name, args, returnType) {
90
+ // Get default fields for a type from schema
91
+ function getDefaultFieldsForType(typeName, schema) {
92
+ const type = schema.getType(typeName);
93
+ if (!type || !('getFields' in type)) return [];
94
+ const fields = type.getFields();
95
+ const fieldNames = Object.keys(fields);
96
+ // Priority order for default fields
97
+ const priorityFields = [
98
+ 'code',
99
+ 'id',
100
+ 'name',
101
+ 'title',
102
+ 'slug'
103
+ ];
104
+ const selectedFields = [];
105
+ // First, add priority fields if they exist
106
+ for (const pf of priorityFields){
107
+ if (fieldNames.includes(pf)) {
108
+ selectedFields.push(pf);
109
+ if (selectedFields.length >= 3) break;
110
+ }
111
+ }
112
+ // If we don't have enough, add other scalar fields
113
+ if (selectedFields.length < 3) {
114
+ for (const [fname, fdef] of Object.entries(fields)){
115
+ if (selectedFields.includes(fname)) continue;
116
+ const ftype = graphqlTypeToString(fdef.type).replace(/[\[\]!]/g, '').trim();
117
+ if ([
118
+ 'String',
119
+ 'Int',
120
+ 'Float',
121
+ 'Boolean',
122
+ 'ID'
123
+ ].includes(ftype)) {
124
+ selectedFields.push(fname);
125
+ if (selectedFields.length >= 3) break;
126
+ }
127
+ }
128
+ }
129
+ return selectedFields.length > 0 ? selectedFields : [
130
+ '__typename'
131
+ ];
132
+ }
133
+ // Generate example GraphQL query from schema field
134
+ function generateGraphQLQueryFromField(operationType, name, args, returnType, schema) {
90
135
  let query = `${operationType} ${name.charAt(0).toUpperCase() + name.slice(1)}`;
91
136
  if (args.length > 0) {
92
- const varDefs = args.map((arg)=>`$${arg.name.value}: ${typeNodeToString(arg.type)}`).join(', ');
137
+ const varDefs = args.map((arg)=>`$${arg.name}: ${graphqlTypeToString(arg.type)}`).join(', ');
93
138
  query += `(${varDefs})`;
94
139
  }
95
140
  query += ` {\n ${name}`;
96
141
  if (args.length > 0) {
97
- const argPairs = args.map((arg)=>`${arg.name.value}: $${arg.name.value}`).join(', ');
142
+ const argPairs = args.map((arg)=>`${arg.name}: $${arg.name}`).join(', ');
98
143
  query += `(${argPairs})`;
99
144
  }
100
145
  const baseType = returnType.replace(/[\[\]!]/g, '').trim();
@@ -107,34 +152,126 @@ function generateGraphQLQuery(operationType, name, args, returnType) {
107
152
  ].includes(baseType)) {
108
153
  query += '\n}';
109
154
  } else {
110
- query += ` {\n id\n __typename\n }\n}`;
155
+ // Get actual fields from the schema for this type
156
+ const defaultFields = getDefaultFieldsForType(baseType, schema);
157
+ const fieldsStr = defaultFields.map((f)=>` ${f}`).join('\n');
158
+ query += ` {\n${fieldsStr}\n }\n}`;
111
159
  }
112
160
  return query;
113
161
  }
114
- // Generate example variables from AST args
115
- function generateGraphQLVariables(args) {
162
+ // Generate example value for a GraphQL type
163
+ function generateExampleValue(type, schema, depth = 0) {
164
+ // Prevent infinite recursion
165
+ if (depth > 3) return null;
166
+ // Unwrap NonNull
167
+ if (isNonNullType(type)) {
168
+ return generateExampleValue(type.ofType, schema, depth);
169
+ }
170
+ // Handle List types - return array with one example element
171
+ if (isListType(type)) {
172
+ const itemValue = generateExampleValue(type.ofType, schema, depth);
173
+ return itemValue !== null ? [
174
+ itemValue
175
+ ] : [];
176
+ }
177
+ // Get the named type
178
+ const typeName = type.name;
179
+ if (!typeName) return null;
180
+ // Built-in scalar types
181
+ switch(typeName){
182
+ case 'String':
183
+ return 'example';
184
+ case 'Int':
185
+ return 10;
186
+ case 'Float':
187
+ return 1.5;
188
+ case 'Boolean':
189
+ return true;
190
+ case 'ID':
191
+ return 'example-id';
192
+ // Common custom scalars
193
+ case 'DateTime':
194
+ return new Date().toISOString();
195
+ case 'Date':
196
+ return new Date().toISOString().split('T')[0];
197
+ case 'Time':
198
+ return '12:00:00';
199
+ case 'JSON':
200
+ return {};
201
+ case 'JSONObject':
202
+ return {};
203
+ case 'URL':
204
+ return 'https://example.com';
205
+ case 'URI':
206
+ return 'https://example.com';
207
+ case 'Email':
208
+ return 'user@example.com';
209
+ case 'EmailAddress':
210
+ return 'user@example.com';
211
+ case 'UUID':
212
+ return '550e8400-e29b-41d4-a716-446655440000';
213
+ case 'BigInt':
214
+ return '9007199254740991';
215
+ case 'Long':
216
+ return 9007199254740991;
217
+ }
218
+ // Check if it's an enum type
219
+ const schemaType = schema.getType(typeName);
220
+ if (schemaType && 'getValues' in schemaType) {
221
+ const enumType = schemaType;
222
+ const values = enumType.getValues();
223
+ if (values.length > 0) {
224
+ return values[0].name // Return first enum value
225
+ ;
226
+ }
227
+ }
228
+ // Check if it's an input object type
229
+ if (schemaType && 'getFields' in schemaType) {
230
+ const inputType = schemaType;
231
+ const fields = inputType.getFields();
232
+ const result = {};
233
+ // Only include required fields to keep examples concise
234
+ for (const [fieldName, field] of Object.entries(fields)){
235
+ if (isNonNullType(field.type)) {
236
+ // Use default value if available, otherwise generate
237
+ if (field.defaultValue !== undefined) {
238
+ result[fieldName] = field.defaultValue;
239
+ } else {
240
+ result[fieldName] = generateExampleValue(field.type, schema, depth + 1);
241
+ }
242
+ }
243
+ }
244
+ // If no required fields, include first optional field
245
+ if (Object.keys(result).length === 0) {
246
+ const firstField = Object.entries(fields)[0];
247
+ if (firstField) {
248
+ const [fname, fdef] = firstField;
249
+ // Use default value if available
250
+ if (fdef.defaultValue !== undefined) {
251
+ result[fname] = fdef.defaultValue;
252
+ } else {
253
+ result[fname] = generateExampleValue(fdef.type, schema, depth + 1);
254
+ }
255
+ }
256
+ }
257
+ return result;
258
+ }
259
+ // Unknown type - return null
260
+ return null;
261
+ }
262
+ // Generate example variables from GraphQL arguments
263
+ function generateGraphQLVariablesFromArgs(args, schema) {
116
264
  const variables = {};
117
265
  for (const arg of args){
118
- const name = arg.name.value;
119
- const type = typeNodeToString(arg.type).replace(/[\[\]!]/g, '').trim();
120
- switch(type){
121
- case 'String':
122
- variables[name] = 'example';
123
- break;
124
- case 'Int':
125
- variables[name] = 1;
126
- break;
127
- case 'Float':
128
- variables[name] = 1.0;
129
- break;
130
- case 'Boolean':
131
- variables[name] = true;
132
- break;
133
- case 'ID':
134
- variables[name] = '1';
135
- break;
136
- default:
137
- variables[name] = {};
266
+ // First, check if the argument has a default value in the schema
267
+ if (arg.defaultValue !== undefined) {
268
+ variables[arg.name] = arg.defaultValue;
269
+ } else {
270
+ // Generate example value for arguments without defaults
271
+ const value = generateExampleValue(arg.type, schema, 0);
272
+ if (value !== null) {
273
+ variables[arg.name] = value;
274
+ }
138
275
  }
139
276
  }
140
277
  return variables;
@@ -271,18 +408,45 @@ function getFirstDocPageForTab(docGroups, tabId) {
271
408
  }
272
409
  function DocsContent() {
273
410
  const [collection, setCollection] = useState(null);
274
- const [selectedRequest, setSelectedRequest] = useState(null);
275
- const [selectedDocSection, setSelectedDocSection] = useState(null);
276
- const [selectedDocPage, setSelectedDocPage] = useState(null);
277
- const [activeTab, setActiveTab] = useState('api-reference');
278
411
  const [selectedApiVersion, setSelectedApiVersion] = useState(null);
279
412
  const [loading, setLoading] = useState(true);
280
413
  const [isVersionLoading, setIsVersionLoading] = useState(false) // For version switch only
281
414
  ;
282
415
  const [error, setError] = useState(null);
283
416
  const [showAuthModal, setShowAuthModal] = useState(false);
284
- const [notFoundSlug, setNotFoundSlug] = useState(null) // Track 404 for invalid URLs
285
- ;
417
+ // URL-based route state - single source of truth for selection
418
+ const routeState = useRouteState();
419
+ // Derive active tab from URL (with default fallback)
420
+ const activeTab = routeState.tab || 'api-reference';
421
+ // Derive selected doc page from URL
422
+ const selectedDocPage = routeState.contentType === 'page' ? routeState.contentId : null;
423
+ // Derive selected doc section from URL
424
+ const selectedDocSection = routeState.contentType === 'section' ? routeState.contentId : null;
425
+ // Derive selected request from URL by looking up in collection
426
+ const selectedRequest = useMemo(()=>{
427
+ if (routeState.contentType !== 'endpoint' || !routeState.contentId || !collection) {
428
+ return null;
429
+ }
430
+ return findRequestById(collection, routeState.contentId);
431
+ }, [
432
+ routeState.contentType,
433
+ routeState.contentId,
434
+ collection
435
+ ]);
436
+ // Derive not found slug from URL when endpoint doesn't exist
437
+ const notFoundSlug = useMemo(()=>{
438
+ if (routeState.contentType === 'endpoint' && routeState.contentId && collection) {
439
+ const request = findRequestById(collection, routeState.contentId);
440
+ if (!request) {
441
+ return `endpoint/${routeState.contentId}`;
442
+ }
443
+ }
444
+ return null;
445
+ }, [
446
+ routeState.contentType,
447
+ routeState.contentId,
448
+ collection
449
+ ]);
286
450
  // Prefill context for agent
287
451
  const { setPrefill } = usePlaygroundPrefill();
288
452
  // Playground navigation context for tab navigation
@@ -291,146 +455,36 @@ function DocsContent() {
291
455
  const { switchToDocs } = useModeContext();
292
456
  // Ref for the scrollable content area
293
457
  const contentRef = useRef(null);
294
- // Update URL hash without triggering navigation
295
- // Format: #tab or #tab/type/id (e.g., #api-reference, #api-reference/endpoint/123, #guides/page/quickstart)
296
- const updateUrlHash = useCallback((hash, tab)=>{
297
- const currentTab = tab || activeTab;
298
- let newUrl;
299
- if (!hash) {
300
- // Just the tab
301
- newUrl = `#${currentTab}`;
302
- } else {
303
- // Tab + path
304
- newUrl = `#${currentTab}/${hash}`;
305
- }
306
- window.history.pushState(null, '', newUrl);
307
- }, [
308
- activeTab
309
- ]);
310
- // Navigate to hash - used for initial load and popstate
311
- // Parse hash format: #tab or #tab/type/id
312
- const parseHash = useCallback((hash)=>{
313
- if (!hash) return {
314
- tab: null,
315
- type: null,
316
- id: null
317
- };
318
- const parts = hash.split('/');
319
- const tab = parts[0] || null;
320
- const type = parts[1] || null;
321
- const id = parts.slice(2).join('/') || null // Rejoin in case id has slashes
322
- ;
323
- return {
324
- tab,
325
- type,
326
- id
327
- };
328
- }, []);
329
- const navigateToHash = useCallback((collectionData)=>{
330
- const hash = window.location.hash.slice(1) // Remove #
331
- ;
332
- if (!hash) {
333
- // No hash - auto-select first endpoint
334
- setSelectedDocSection(null);
335
- setSelectedDocPage(null);
336
- const firstEndpoint = getFirstEndpoint(collectionData);
337
- if (firstEndpoint) {
338
- setSelectedRequest(firstEndpoint);
339
- } else {
340
- setSelectedRequest(null);
341
- }
342
- return;
343
- }
344
- // Notes mode is handled by ModeContext, just clear API selection
345
- if (hash === 'notes' || hash.startsWith('notes/')) {
346
- setSelectedRequest(null);
347
- setSelectedDocSection(null);
348
- setSelectedDocPage(null);
349
- return;
350
- }
351
- // Parse new format: #tab/type/id
352
- const { tab, type, id } = parseHash(hash);
353
- // Set the active tab if specified
354
- if (tab) {
355
- setActiveTab(tab);
356
- }
357
- // Handle legacy format (endpoint/xxx, page/xxx, doc/xxx without tab prefix)
358
- const legacyType = hash.startsWith('endpoint/') ? 'endpoint' : hash.startsWith('page/') ? 'page' : hash.startsWith('doc/') ? 'doc' : null;
359
- const actualType = type || legacyType;
360
- const actualId = id || (legacyType ? hash.replace(`${legacyType}/`, '') : null);
361
- if (actualType === 'endpoint' && actualId) {
362
- const request = findRequestById(collectionData, actualId);
363
- if (request) {
364
- setSelectedRequest(request);
365
- setSelectedDocSection(null);
366
- setSelectedDocPage(null);
367
- setNotFoundSlug(null);
368
- } else {
369
- // Endpoint not found - show 404 page
370
- setSelectedRequest(null);
371
- setSelectedDocSection(null);
372
- setSelectedDocPage(null);
373
- setNotFoundSlug(`endpoint/${actualId}`);
374
- }
375
- } else if (actualType === 'page' && actualId) {
376
- setNotFoundSlug(null); // Clear not found state - DocPage handles its own 404
377
- setSelectedDocPage(actualId);
378
- setSelectedRequest(null);
379
- setSelectedDocSection(null);
380
- } else if (actualType === 'doc' && actualId) {
381
- setSelectedDocSection(actualId);
382
- setSelectedRequest(null);
383
- setSelectedDocPage(null);
384
- // Scroll to section after DOM update
385
- setTimeout(()=>{
386
- const element = document.getElementById(actualId);
387
- if (element) {
388
- element.scrollIntoView({
389
- behavior: 'smooth',
390
- block: 'start'
391
- });
392
- }
393
- }, 100);
394
- } else if (tab && !type) {
395
- // Just a tab, no specific content - show default for that tab
396
- setSelectedRequest(null);
397
- setSelectedDocSection(null);
398
- setSelectedDocPage(null);
399
- }
400
- }, [
401
- parseHash
402
- ]);
458
+ // Track if initial navigation has been done (prevents re-navigation on effect re-runs)
459
+ const hasInitialNavigated = useRef(false);
460
+ // Handle selecting a request - just navigates to URL
403
461
  const handleSelectRequest = useCallback((request)=>{
404
- setSelectedRequest(request);
405
- setSelectedDocSection(null);
406
- setSelectedDocPage(null);
407
- setNotFoundSlug(null); // Clear 404 state when selecting an endpoint
408
- updateUrlHash(`endpoint/${request.id}`);
462
+ navigateToEndpoint(activeTab, request.id);
409
463
  // Reset tab navigation so the new endpoint can determine its default tab
410
464
  resetNavigation();
411
465
  // Switch to Docs mode to show endpoint documentation first
412
466
  switchToDocs();
413
467
  }, [
414
- updateUrlHash,
468
+ activeTab,
415
469
  resetNavigation,
416
470
  switchToDocs
417
471
  ]);
472
+ // Handle selecting a documentation section (scroll to heading)
418
473
  const handleSelectDocumentation = useCallback((headingId)=>{
419
474
  const isIntro = headingId === 'introduction';
420
- setSelectedDocSection(isIntro ? null : headingId);
421
- setSelectedRequest(null);
422
- setSelectedDocPage(null);
423
- setNotFoundSlug(null); // Clear 404 state
424
- updateUrlHash(isIntro ? '' : `doc/${headingId}`);
425
- // Switch to Docs mode when selecting documentation
426
- switchToDocs();
427
- setTimeout(()=>{
428
- if (isIntro) {
475
+ if (isIntro) {
476
+ // Scroll to top
477
+ navigateToTab(activeTab);
478
+ setTimeout(()=>{
429
479
  contentRef.current?.scrollTo({
430
480
  top: 0,
431
481
  behavior: 'smooth'
432
482
  });
433
- } else {
483
+ }, 50);
484
+ } else {
485
+ // Navigate to section and scroll
486
+ navigateToSection(activeTab, headingId);
487
+ setTimeout(()=>{
434
488
  const element = document.getElementById(headingId);
435
489
  if (element) {
436
490
  element.scrollIntoView({
@@ -438,12 +492,15 @@ function DocsContent() {
438
492
  block: 'start'
439
493
  });
440
494
  }
441
- }
442
- }, 50);
495
+ }, 50);
496
+ }
497
+ // Switch to Docs mode when selecting documentation
498
+ switchToDocs();
443
499
  }, [
444
- updateUrlHash,
500
+ activeTab,
445
501
  switchToDocs
446
502
  ]);
503
+ // Handle selecting a doc page
447
504
  const handleSelectDocPage = useCallback((slug)=>{
448
505
  // Find which tab this doc page belongs to
449
506
  let targetTab = activeTab;
@@ -488,13 +545,8 @@ function DocsContent() {
488
545
  }
489
546
  }
490
547
  }
491
- // Switch to the correct tab
492
- setActiveTab(targetTab);
493
- setSelectedDocPage(pageSlug);
494
- setSelectedRequest(null);
495
- setSelectedDocSection(null);
496
- setNotFoundSlug(null); // Clear 404 state
497
- updateUrlHash(`page/${pageSlug}`, targetTab);
548
+ // Navigate to the page via URL
549
+ navigateToPage(targetTab, pageSlug);
498
550
  switchToDocs();
499
551
  // Scroll to specific release if navigating to a changelog entry
500
552
  if (releaseSlug) {
@@ -529,7 +581,6 @@ function DocsContent() {
529
581
  }, 50);
530
582
  }
531
583
  }, [
532
- updateUrlHash,
533
584
  switchToDocs,
534
585
  collection,
535
586
  activeTab
@@ -538,16 +589,15 @@ function DocsContent() {
538
589
  const handleApiVersionChange = useCallback((version)=>{
539
590
  if (version !== selectedApiVersion) {
540
591
  setSelectedApiVersion(version);
541
- // Clear selected request when switching versions as endpoints may differ
542
- setSelectedRequest(null);
543
- setSelectedDocSection(null);
592
+ // Navigate to tab only (clears selection) when switching versions
593
+ navigateToTab(activeTab);
544
594
  }
545
595
  }, [
546
- selectedApiVersion
596
+ selectedApiVersion,
597
+ activeTab
547
598
  ]);
548
599
  // Handle tab change from header
549
600
  const handleTabChange = useCallback((tabId)=>{
550
- setActiveTab(tabId);
551
601
  // Reset mode to docs when switching tabs (exit sandbox/api-client mode)
552
602
  switchToDocs();
553
603
  // Find the tab config to check its type
@@ -555,68 +605,51 @@ function DocsContent() {
555
605
  const isApiTab = tabConfig?.type === 'openapi' || tabConfig?.type === 'graphql' || tabId === 'api-reference';
556
606
  if (tabId === 'changelog') {
557
607
  // Switch to Changelog tab
558
- setSelectedDocPage(null);
559
- setSelectedRequest(null);
560
- setSelectedDocSection(null);
561
- updateUrlHash('', tabId);
608
+ navigateToTab(tabId);
562
609
  } else if (isApiTab) {
563
610
  // API Reference or GraphQL tab
564
- setSelectedDocSection(null);
565
611
  // Check if there are doc groups for this tab (MDX pages)
566
612
  const hasGroups = hasDocGroupsForTab(collection?.docGroups, tabId);
567
613
  if (hasGroups) {
568
614
  // Has doc groups - select the first page from groups
569
615
  const firstPage = getFirstDocPageForTab(collection?.docGroups, tabId);
570
616
  if (firstPage) {
571
- setSelectedDocPage(firstPage);
572
- setSelectedRequest(null);
573
- updateUrlHash(`page/${firstPage}`, tabId);
617
+ navigateToPage(tabId, firstPage);
574
618
  switchToDocs();
575
619
  } else {
576
620
  // No pages in groups - auto-select first endpoint
577
- setSelectedDocPage(null);
578
621
  const firstEndpoint = collection ? getFirstEndpoint(collection) : null;
579
622
  if (firstEndpoint) {
580
- setSelectedRequest(firstEndpoint);
581
- updateUrlHash(`endpoint/${firstEndpoint.id}`, tabId);
623
+ navigateToEndpoint(tabId, firstEndpoint.id);
582
624
  } else {
583
- setSelectedRequest(null);
584
- updateUrlHash('', tabId);
625
+ navigateToTab(tabId);
585
626
  }
586
627
  }
587
628
  } else {
588
629
  // No doc groups - auto-select first endpoint
589
- setSelectedDocPage(null);
590
630
  const firstEndpoint = collection ? getFirstEndpoint(collection) : null;
591
631
  if (firstEndpoint) {
592
- setSelectedRequest(firstEndpoint);
593
- updateUrlHash(`endpoint/${firstEndpoint.id}`, tabId);
632
+ navigateToEndpoint(tabId, firstEndpoint.id);
594
633
  } else {
595
- setSelectedRequest(null);
596
- updateUrlHash('', tabId);
634
+ navigateToTab(tabId);
597
635
  }
598
636
  }
599
637
  } else {
600
638
  // Switch to a doc group tab - find and select the first page in that tab
601
- setSelectedRequest(null);
602
- setSelectedDocSection(null);
603
639
  // Find the first doc group for this tab and select its first page
604
640
  if (collection?.docGroups) {
605
641
  const tabDocGroup = collection.docGroups.find((g)=>isGroupForTab(g.id, tabId));
606
642
  if (tabDocGroup && tabDocGroup.pages.length > 0) {
607
643
  const firstPage = tabDocGroup.pages[0];
608
- setSelectedDocPage(firstPage.slug);
609
- updateUrlHash(`page/${firstPage.slug}`, tabId);
644
+ navigateToPage(tabId, firstPage.slug);
610
645
  switchToDocs();
611
646
  } else {
612
- setSelectedDocPage(null);
613
- updateUrlHash('', tabId);
647
+ navigateToTab(tabId);
614
648
  }
615
649
  }
616
650
  }
617
651
  }, [
618
652
  collection,
619
- updateUrlHash,
620
653
  switchToDocs
621
654
  ]);
622
655
  // Handler for agent navigation
@@ -624,19 +657,13 @@ function DocsContent() {
624
657
  if (!collection) return;
625
658
  const request = findRequestById(collection, endpointId);
626
659
  if (request) {
627
- // Set the tab to API Reference first
628
- setActiveTab('api-reference');
629
- // Then set the selected request
630
- setSelectedRequest(request);
631
- setSelectedDocSection(null);
632
- setSelectedDocPage(null);
633
- updateUrlHash(`endpoint/${endpointId}`, 'api-reference');
660
+ // Navigate to the endpoint in api-reference tab
661
+ navigateToEndpoint('api-reference', endpointId);
634
662
  // Reset tab navigation so the new endpoint can determine its default tab
635
663
  resetNavigation();
636
664
  }
637
665
  }, [
638
666
  collection,
639
- updateUrlHash,
640
667
  resetNavigation
641
668
  ]);
642
669
  // Handler for agent prefilling parameters
@@ -666,18 +693,7 @@ function DocsContent() {
666
693
  const clearExplainContext = useCallback(()=>{
667
694
  setExplainContext(null);
668
695
  }, []);
669
- // Handle browser back/forward
670
- useEffect(()=>{
671
- if (!collection) return;
672
- const handlePopState = ()=>{
673
- navigateToHash(collection);
674
- };
675
- window.addEventListener('popstate', handlePopState);
676
- return ()=>window.removeEventListener('popstate', handlePopState);
677
- }, [
678
- collection,
679
- navigateToHash
680
- ]);
696
+ // Note: Browser back/forward is now handled by useRouteState hook automatically
681
697
  // Dynamically set favicon from docs.json config
682
698
  useEffect(()=>{
683
699
  if (!collection?.docsFavicon) return;
@@ -733,184 +749,53 @@ function DocsContent() {
733
749
  if (!selectedApiVersion && data?.selectedApiVersion) {
734
750
  setSelectedApiVersion(data.selectedApiVersion);
735
751
  }
736
- // Only run initial navigation logic on first load, not on version changes
737
- if (isInitialLoad) {
738
- // Set initial active tab from config (first tab) and select first item
739
- let initialTabId = 'api-reference';
740
- if (data?.navigationTabs && data.navigationTabs.length > 0) {
752
+ // Only run initial navigation logic on first load
753
+ if (isInitialLoad && !hasInitialNavigated.current) {
754
+ hasInitialNavigated.current = true;
755
+ // Helper: Navigate to first content for a given tab
756
+ const navigateToFirstContent = (tabId)=>{
757
+ const tabConfig = data.navigationTabs?.find((t)=>t.id === tabId);
758
+ const isApiTab = tabConfig?.type === 'openapi' || tabConfig?.type === 'graphql' || tabId === 'api-reference';
759
+ // Try doc pages first, then endpoints
760
+ const firstPage = isApiTab ? getFirstDocPageForTab(data.docGroups, tabId) : data.docGroups?.find((g)=>isGroupForTab(g.id, tabId))?.pages[0]?.slug;
761
+ if (firstPage) {
762
+ navigateToPage(tabId, firstPage);
763
+ return;
764
+ }
765
+ // Fall back to first endpoint for API tabs
766
+ if (isApiTab) {
767
+ const firstEndpoint = getFirstEndpoint(data);
768
+ if (firstEndpoint) {
769
+ navigateToEndpoint(tabId, firstEndpoint.id);
770
+ return;
771
+ }
772
+ }
773
+ // No content found, just navigate to tab
774
+ navigateToTab(tabId);
775
+ };
776
+ // Get default tab (first tab in config)
777
+ let defaultTabId = 'api-reference';
778
+ if (data?.navigationTabs?.length > 0) {
741
779
  const sortedTabs = [
742
780
  ...data.navigationTabs
743
781
  ].sort((a, b)=>a.order - b.order);
744
- initialTabId = sortedTabs[0].id;
745
- setActiveTab(initialTabId);
746
- }
747
- // Handle initial hash navigation after collection loads
748
- if (data) {
749
- // Use setTimeout to ensure state is set before navigation
750
- setTimeout(()=>{
751
- const hash = window.location.hash.slice(1);
752
- if (!hash) {
753
- // No hash - set URL to initial tab
754
- setSelectedDocSection(null);
755
- // Find the tab config to check its type
756
- const tabConfig = data.navigationTabs?.find((t)=>t.id === initialTabId);
757
- const isApiTab = tabConfig?.type === 'openapi' || tabConfig?.type === 'graphql' || initialTabId === 'api-reference';
758
- if (isApiTab) {
759
- // API Reference or GraphQL tab
760
- setSelectedDocSection(null);
761
- // Check if there are doc groups for this tab (MDX pages)
762
- const hasGroups = hasDocGroupsForTab(data.docGroups, initialTabId);
763
- if (hasGroups) {
764
- // Has doc groups - select the first page from groups
765
- const firstPage = getFirstDocPageForTab(data.docGroups, initialTabId);
766
- if (firstPage) {
767
- setSelectedDocPage(firstPage);
768
- setSelectedRequest(null);
769
- updateUrlHash(`page/${firstPage}`, initialTabId);
770
- } else {
771
- // No pages in groups - auto-select first endpoint
772
- setSelectedDocPage(null);
773
- const firstEndpoint = getFirstEndpoint(data);
774
- if (firstEndpoint) {
775
- setSelectedRequest(firstEndpoint);
776
- updateUrlHash(`endpoint/${firstEndpoint.id}`, initialTabId);
777
- } else {
778
- setSelectedRequest(null);
779
- updateUrlHash('', initialTabId);
780
- }
781
- }
782
- } else {
783
- // No doc groups - auto-select first endpoint
784
- setSelectedDocPage(null);
785
- const firstEndpoint = getFirstEndpoint(data);
786
- if (firstEndpoint) {
787
- setSelectedRequest(firstEndpoint);
788
- updateUrlHash(`endpoint/${firstEndpoint.id}`, initialTabId);
789
- } else {
790
- setSelectedRequest(null);
791
- updateUrlHash('', initialTabId);
792
- }
793
- }
794
- switchToDocs();
795
- } else {
796
- // Doc group tab - select first page
797
- setSelectedRequest(null);
798
- const tabDocGroup = data.docGroups?.find((g)=>isGroupForTab(g.id, initialTabId));
799
- if (tabDocGroup && tabDocGroup.pages.length > 0) {
800
- const firstPage = tabDocGroup.pages[0];
801
- setSelectedDocPage(firstPage.slug);
802
- updateUrlHash(`page/${firstPage.slug}`, initialTabId);
803
- switchToDocs();
804
- } else {
805
- setSelectedDocPage(null);
806
- updateUrlHash('', initialTabId);
807
- switchToDocs();
808
- }
809
- }
810
- } else if (hash === 'notes' || hash.startsWith('notes/')) {
811
- // Notes mode - handled by ModeContext, just clear API selection
812
- setSelectedRequest(null);
813
- setSelectedDocSection(null);
814
- } else {
815
- // Parse the hash to get tab and content info
816
- // Format: #tab or #tab/type/id (e.g., #api-reference/endpoint/123)
817
- const parts = hash.split('/');
818
- const hashTab = parts[0];
819
- const hashType = parts[1];
820
- const hashId = parts.slice(2).join('/');
821
- // Set the tab from the hash
822
- if (hashTab && data.navigationTabs?.some((t)=>t.id === hashTab)) {
823
- setActiveTab(hashTab);
824
- }
825
- // Handle legacy format (endpoint/xxx without tab prefix)
826
- const isLegacyFormat = hash.startsWith('endpoint/') || hash.startsWith('page/') || hash.startsWith('doc/');
827
- const actualType = isLegacyFormat ? parts[0] : hashType;
828
- const actualId = isLegacyFormat ? parts.slice(1).join('/') : hashId;
829
- if (actualType === 'endpoint' && actualId) {
830
- const request = findRequestById(data, actualId);
831
- if (request) {
832
- setSelectedRequest(request);
833
- setSelectedDocSection(null);
834
- setSelectedDocPage(null);
835
- switchToDocs();
836
- return;
837
- } else {
838
- setSelectedRequest(null);
839
- setSelectedDocSection(null);
840
- setSelectedDocPage(null);
841
- switchToDocs();
842
- }
843
- } else if (actualType === 'page' && actualId) {
844
- setSelectedDocPage(actualId);
845
- setSelectedRequest(null);
846
- setSelectedDocSection(null);
847
- switchToDocs();
848
- } else if (actualType === 'doc' && actualId) {
849
- setSelectedDocSection(actualId);
850
- setSelectedRequest(null);
851
- setSelectedDocPage(null);
852
- switchToDocs();
853
- setTimeout(()=>{
854
- const element = document.getElementById(actualId);
855
- if (element) {
856
- element.scrollIntoView({
857
- behavior: 'smooth',
858
- block: 'start'
859
- });
860
- }
861
- }, 100);
862
- } else if (hashTab && !hashType) {
863
- // Just a tab, show its default content
864
- setSelectedDocSection(null);
865
- // Check if this is an API tab
866
- const tabConfig = data.navigationTabs?.find((t)=>t.id === hashTab);
867
- const isApiTab = tabConfig?.type === 'openapi' || tabConfig?.type === 'graphql' || hashTab === 'api-reference';
868
- if (isApiTab) {
869
- // Check if there are doc groups for this tab (MDX pages)
870
- const hasGroups = hasDocGroupsForTab(data.docGroups, hashTab);
871
- if (hasGroups) {
872
- // Has doc groups - select the first page from groups
873
- const firstPage = getFirstDocPageForTab(data.docGroups, hashTab);
874
- if (firstPage) {
875
- setSelectedDocPage(firstPage);
876
- setSelectedRequest(null);
877
- updateUrlHash(`page/${firstPage}`, hashTab);
878
- } else {
879
- // No pages in groups - auto-select first endpoint
880
- setSelectedDocPage(null);
881
- const firstEndpoint = getFirstEndpoint(data);
882
- if (firstEndpoint) {
883
- setSelectedRequest(firstEndpoint);
884
- updateUrlHash(`endpoint/${firstEndpoint.id}`, hashTab);
885
- } else {
886
- setSelectedRequest(null);
887
- }
888
- }
889
- } else {
890
- // No doc groups - auto-select first endpoint
891
- setSelectedDocPage(null);
892
- const firstEndpoint = getFirstEndpoint(data);
893
- if (firstEndpoint) {
894
- setSelectedRequest(firstEndpoint);
895
- updateUrlHash(`endpoint/${firstEndpoint.id}`, hashTab);
896
- } else {
897
- setSelectedRequest(null);
898
- }
899
- }
900
- } else {
901
- setSelectedDocPage(null);
902
- setSelectedRequest(null);
903
- }
904
- switchToDocs();
905
- }
906
- }
907
- }, 0);
782
+ defaultTabId = sortedTabs[0].id;
908
783
  }
909
- } else {
910
- // Version change - clear selected endpoint as it may not exist in the new version
911
- setSelectedRequest(null);
912
- setSelectedDocSection(null);
784
+ // Handle initial navigation after collection loads
785
+ setTimeout(()=>{
786
+ const hash = window.location.hash.slice(1);
787
+ const isTabOnly = hash && !hash.includes('/');
788
+ if (!hash) {
789
+ // No hash - navigate to first content of default tab
790
+ navigateToFirstContent(defaultTabId);
791
+ } else if (isTabOnly) {
792
+ // Just a tab name (e.g., #guides) - navigate to first content
793
+ navigateToFirstContent(hash);
794
+ }
795
+ // View mode (docs/playground/notes) is derived from URL by ModeProvider
796
+ }, 0);
913
797
  }
798
+ // Version changes are handled by handleApiVersionChange callback
914
799
  } catch (err) {
915
800
  console.error('Error fetching collection:', err);
916
801
  setError(err instanceof Error ? err.message : 'Failed to load API documentation');
@@ -922,7 +807,6 @@ function DocsContent() {
922
807
  fetchCollection();
923
808
  // eslint-disable-next-line react-hooks/exhaustive-deps
924
809
  }, [
925
- switchToDocs,
926
810
  selectedApiVersion
927
811
  ]);
928
812
  // Only show full-page loading on initial load
@@ -1165,10 +1049,29 @@ function ModeToggleTabs({ hasEndpoint }) {
1165
1049
  function DocsWithMode({ collection, selectedRequest, selectedDocSection, selectedDocPage, activeTab, onTabChange, contentRef, showAuthModal, setShowAuthModal, handleSelectRequest, handleSelectDocumentation, handleSelectDocPage, handleDebugRequest, handleExplainRequest, handleAgentNavigate, handleAgentPrefill, debugContext, clearDebugContext, explainContext, clearExplainContext, navigateAndHighlight, selectedApiVersion, handleApiVersionChange, isVersionLoading, notFoundSlug }) {
1166
1050
  const { mode, switchToDocs } = useModeContext();
1167
1051
  const { setTheme } = useTheme();
1052
+ // Agent panel state - controls popup button and push animation
1053
+ const { isRightSidebarOpen, openRightSidebar } = useMobile();
1054
+ // Build endpoint index for suggestions (memoized)
1055
+ const endpointIndex = useMemo(()=>buildEndpointIndex(collection), [
1056
+ collection
1057
+ ]);
1168
1058
  // GraphQL state
1169
1059
  const [graphqlOperations, setGraphqlOperations] = useState([]);
1170
1060
  const [graphqlCollection, setGraphqlCollection] = useState(null);
1171
- const [selectedGraphQLOperation, setSelectedGraphQLOperation] = useState(null);
1061
+ const [graphqlSchemaSDL, setGraphqlSchemaSDL] = useState(undefined);
1062
+ // Use route state for URL-based selection
1063
+ const routeState = useRouteState();
1064
+ // Derive selected GraphQL operation from URL
1065
+ const selectedGraphQLOperation = useMemo(()=>{
1066
+ if (routeState.contentType !== 'endpoint' || !routeState.contentId) {
1067
+ return null;
1068
+ }
1069
+ return graphqlOperations.find((op)=>op.id === routeState.contentId) || null;
1070
+ }, [
1071
+ routeState.contentType,
1072
+ routeState.contentId,
1073
+ graphqlOperations
1074
+ ]);
1172
1075
  // Get API spec URL from environment or collection
1173
1076
  const apiSpecUrl = process.env.NEXT_PUBLIC_OPENAPI_URL || collection.name || 'default';
1174
1077
  // Check if there are endpoints
@@ -1193,7 +1096,7 @@ function DocsWithMode({ collection, selectedRequest, selectedDocSection, selecte
1193
1096
  }, []);
1194
1097
  // Wrap agent navigate to switch to Docs mode and navigate to endpoint
1195
1098
  const handleAgentNavigateWithModeSwitch = useCallback((endpointId)=>{
1196
- // Just navigate - handleAgentNavigate already sets the tab via setActiveTab
1099
+ // handleAgentNavigate navigates via URL
1197
1100
  handleAgentNavigate(endpointId);
1198
1101
  }, [
1199
1102
  handleAgentNavigate
@@ -1222,22 +1125,29 @@ function DocsWithMode({ collection, selectedRequest, selectedDocSection, selecte
1222
1125
  if (!showGraphQL || !schemaPath) {
1223
1126
  setGraphqlOperations([]);
1224
1127
  setGraphqlCollection(null);
1225
- setSelectedGraphQLOperation(null); // Clear selection when leaving GraphQL tab
1128
+ setGraphqlSchemaSDL(undefined);
1226
1129
  return;
1227
1130
  }
1228
1131
  // Load and parse GraphQL schema
1229
1132
  const loadGraphQLSchema = async ()=>{
1230
1133
  try {
1134
+ console.log('[DocsViewer] Loading GraphQL schema from:', schemaPath);
1231
1135
  const response = await fetch(`/api/schema?path=${encodeURIComponent(schemaPath)}`);
1232
- if (!response.ok) return;
1136
+ if (!response.ok) {
1137
+ console.error('[DocsViewer] Failed to fetch schema:', response.status);
1138
+ return;
1139
+ }
1233
1140
  const schemaContent = await response.text();
1141
+ console.log('[DocsViewer] Schema loaded, length:', schemaContent.length);
1142
+ setGraphqlSchemaSDL(schemaContent); // Store schema SDL for autocomplete
1234
1143
  const operations = parseGraphQLSchema(schemaContent);
1144
+ console.log('[DocsViewer] Parsed operations:', operations.length);
1235
1145
  setGraphqlOperations(operations);
1236
1146
  // Convert to BrainfishCollection for sidebar
1237
1147
  const gqlCollection = convertGraphQLToCollection(operations, schemaEndpoint || '');
1238
1148
  setGraphqlCollection(gqlCollection);
1239
1149
  } catch (err) {
1240
- console.error('Failed to load GraphQL schema:', err);
1150
+ console.error('[DocsViewer] Failed to load GraphQL schema:', err);
1241
1151
  }
1242
1152
  };
1243
1153
  loadGraphQLSchema();
@@ -1246,58 +1156,34 @@ function DocsWithMode({ collection, selectedRequest, selectedDocSection, selecte
1246
1156
  schemaPath,
1247
1157
  schemaEndpoint
1248
1158
  ]);
1249
- // Auto-select GraphQL operation from URL hash or default to first operation
1159
+ // Auto-navigate to first GraphQL operation when none selected and no doc pages
1250
1160
  useEffect(()=>{
1251
1161
  if (!showGraphQL || graphqlOperations.length === 0) {
1252
1162
  return;
1253
1163
  }
1254
- // Check if there's a selection in the URL hash first
1255
- const hash = window.location.hash.slice(1) // Remove #
1256
- ;
1257
- if (hash) {
1258
- const parts = hash.split('/');
1259
- const hashType = parts[1];
1260
- const hashId = parts.slice(2).join('/');
1261
- if (hashType === 'endpoint' && hashId) {
1262
- // Try to find and select the operation from the URL hash
1263
- const operation = graphqlOperations.find((op)=>op.id === hashId);
1264
- if (operation) {
1265
- setSelectedGraphQLOperation(operation);
1266
- return;
1267
- }
1268
- }
1269
- }
1270
- // If already have a selection, don't override it
1271
- if (selectedGraphQLOperation) {
1164
+ // If there's already content selected (page or endpoint), don't auto-navigate
1165
+ if (routeState.contentType) {
1272
1166
  return;
1273
1167
  }
1274
- // Check if there are doc groups for this tab first
1168
+ // Check if there are doc groups for this tab
1275
1169
  const hasGroups = hasDocGroupsForTab(collection?.docGroups, activeTab);
1276
1170
  if (!hasGroups) {
1277
- // No doc groups - auto-select first GraphQL operation
1171
+ // No doc groups - auto-navigate to first GraphQL operation
1278
1172
  const firstOperation = graphqlOperations[0];
1279
- setSelectedGraphQLOperation(firstOperation);
1280
- window.history.pushState(null, '', `#${activeTab}/endpoint/${firstOperation.id}`);
1173
+ navigateToEndpoint(activeTab, firstOperation.id);
1281
1174
  }
1282
1175
  }, [
1283
1176
  showGraphQL,
1284
1177
  graphqlOperations,
1285
- selectedGraphQLOperation,
1286
1178
  activeTab,
1287
- collection?.docGroups
1179
+ collection?.docGroups,
1180
+ routeState.contentType
1288
1181
  ]);
1289
- // Handle GraphQL operation selection from sidebar
1182
+ // Handle GraphQL operation selection from sidebar - just navigates via URL
1290
1183
  const handleSelectGraphQLOperation = useCallback((request)=>{
1291
- // Find the matching GraphQL operation
1292
- const operation = graphqlOperations.find((op)=>op.id === request.id);
1293
- if (operation) {
1294
- setSelectedGraphQLOperation(operation);
1295
- // Update URL hash
1296
- window.history.pushState(null, '', `#${activeTab}/endpoint/${request.id}`);
1297
- switchToDocs();
1298
- }
1184
+ navigateToEndpoint(activeTab, request.id);
1185
+ switchToDocs();
1299
1186
  }, [
1300
- graphqlOperations,
1301
1187
  activeTab,
1302
1188
  switchToDocs
1303
1189
  ]);
@@ -1381,7 +1267,8 @@ function DocsWithMode({ collection, selectedRequest, selectedDocSection, selecte
1381
1267
  docsNavbar: collection.docsNavbar
1382
1268
  }),
1383
1269
  /*#__PURE__*/ _jsxs("div", {
1384
- className: "docs-layout flex flex-1 overflow-hidden relative z-0 min-h-0",
1270
+ className: cn("docs-layout flex flex-1 overflow-hidden relative z-0 min-h-0", "transition-[margin] duration-300 ease-in-out", // Push content left when agent panel is open (only on larger screens)
1271
+ isRightSidebarOpen && "lg:mr-96"),
1385
1272
  children: [
1386
1273
  !showChangelog && /*#__PURE__*/ _jsx(DocsSidebar, {
1387
1274
  collection: {
@@ -1426,8 +1313,8 @@ function DocsWithMode({ collection, selectedRequest, selectedDocSection, selecte
1426
1313
  activeTab: activeTab,
1427
1314
  children: /*#__PURE__*/ _jsx("div", {
1428
1315
  ref: contentRef,
1429
- className: cn("docs-content-area flex-1 bg-background min-w-0", showChangelog || showGraphQL && selectedGraphQLOperation ? "overflow-hidden flex flex-col" : "overflow-y-auto scroll-smooth"),
1430
- children: showGraphQL && selectedGraphQLOperation ? /*#__PURE__*/ _jsx("div", {
1316
+ className: cn("docs-content-area flex-1 bg-background min-w-0", showChangelog || showGraphQL && selectedGraphQLOperation && !selectedDocPage ? "overflow-hidden flex flex-col" : "overflow-y-auto scroll-smooth"),
1317
+ children: showGraphQL && selectedGraphQLOperation && !selectedDocPage ? /*#__PURE__*/ _jsx("div", {
1431
1318
  className: "flex-1 flex flex-col h-full",
1432
1319
  children: /*#__PURE__*/ _jsx(GraphQLPlayground, {
1433
1320
  endpoint: activeGraphQLSchemas[0]?.endpoint || '',
@@ -1436,7 +1323,8 @@ function DocsWithMode({ collection, selectedRequest, selectedDocSection, selecte
1436
1323
  selectedOperationId: selectedGraphQLOperation?.id,
1437
1324
  hideExplorer: true,
1438
1325
  headers: {},
1439
- theme: "dark"
1326
+ theme: "dark",
1327
+ schemaSDL: graphqlSchemaSDL
1440
1328
  })
1441
1329
  }) : showChangelog ? /*#__PURE__*/ _jsx(ChangelogPage, {
1442
1330
  releases: collection.changelogReleases || [],
@@ -1534,6 +1422,14 @@ function DocsWithMode({ collection, selectedRequest, selectedDocSection, selecte
1534
1422
  /*#__PURE__*/ _jsx(GlobalAuthModal, {
1535
1423
  open: showAuthModal,
1536
1424
  onClose: ()=>setShowAuthModal(false)
1425
+ }),
1426
+ !isRightSidebarOpen && /*#__PURE__*/ _jsx(AgentPopupButton, {
1427
+ onClick: openRightSidebar,
1428
+ currentEndpoint: selectedRequest ? {
1429
+ id: selectedRequest.id,
1430
+ name: selectedRequest.name
1431
+ } : null,
1432
+ endpointIndex: endpointIndex
1537
1433
  })
1538
1434
  ]
1539
1435
  });