Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
a102e5a
fix(nav): sync header tab highlight on FastNav content swaps
khushalsonawat Aug 18, 2026
c2dbfbf
docs(cookbook): add Use Cases and Platform overview pages
khushalsonawat Aug 18, 2026
a6c45d6
feat(nav): restructure cookbook sidebar into use-case sections
khushalsonawat Aug 18, 2026
457c070
docs(cookbook): repoint notebook badges to main
khushalsonawat Aug 18, 2026
adf3e8d
docs(cookbook): align observe-langgraph cookbook with its real run
khushalsonawat Aug 18, 2026
85ab2ef
fix(docs): repair dead external and app links
khushalsonawat Aug 18, 2026
41f59e6
fix(nav): honor URL hash and make scroll resets instant
khushalsonawat Aug 18, 2026
9907ea3
docs(cookbook): rebuild overview as a section hub
khushalsonawat Aug 18, 2026
c6a5fbb
docs(cookbook): rework Get Started batch to the working standard
khushalsonawat Aug 18, 2026
3e45eb9
docs(cookbook): rework Chat & Support Agents batch to the working sta…
khushalsonawat Aug 18, 2026
288c18f
docs(cookbook): rework RAG evaluation batch to the working standard
khushalsonawat Aug 18, 2026
2eba767
docs(cookbook): rework RAG application batch to the working standard
khushalsonawat Aug 18, 2026
fc1ecce
docs(cookbook): rework Voice and Text-to-SQL batches to the working s…
khushalsonawat Aug 18, 2026
b95ff25
docs(cookbook): rework Multi-Agent & Tool Use batch to the working st…
khushalsonawat Aug 18, 2026
e357a73
docs(cookbook): rework Content & Multimodal batch to the working stan…
khushalsonawat Aug 18, 2026
9d1207f
docs(cookbook): rework Tracing & Debugging batch to the working standard
khushalsonawat Aug 18, 2026
82e869e
docs(cookbook): rework Eval Workflows & Datasets batch to the working…
khushalsonawat Aug 18, 2026
431bb86
docs(cookbook): rework Prompts & Optimization batch to the working st…
khushalsonawat Aug 18, 2026
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
52 changes: 50 additions & 2 deletions src/components/FastNav.astro
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
var href = link.getAttribute('href');
if (!href || !href.startsWith('/docs')) return false;
if (href === window.location.pathname) return false;
// Skip links with a hash — the swap scrolls to top, which would lose the
// anchor position. Full navigation handles the scroll-to-heading.
if (link.hash) return false;
// Skip header tabs — they change the sidebar group
if (link.closest('header') || link.closest('[role="tablist"]') || link.getAttribute('role') === 'tab') return false;
// Skip if crossing between API layout and regular layout
Expand Down Expand Up @@ -101,6 +104,7 @@
}

function swapContent(html, href) {
var priorScroll = window.scrollY;
var parser = new DOMParser();
var doc = parser.parseFromString(html, 'text/html');

Expand Down Expand Up @@ -129,6 +133,15 @@
oldSidebar.innerHTML = newSidebar.innerHTML;
}

// Sync header tab highlight — intercepted links can cross tabs (e.g. the
// sidebar dropdown's Reference section links Docs/Cookbooks/SDK to each
// other), and nothing else swaps the header's active state
var newTabs = doc.querySelector('header nav[role="tablist"]');
var oldTabs = document.querySelector('header nav[role="tablist"]');
if (newTabs && oldTabs) {
oldTabs.innerHTML = newTabs.innerHTML;
}

// Re-execute inline scripts in swapped article
// (innerHTML doesn't execute <script> tags — clone them so the browser runs them)
// This populates ApiPlayground code displays, sets up event listeners, etc.
Expand All @@ -154,10 +167,22 @@
}
}

// Scroll content to top
// Scroll content to top. Must be behavior: 'instant' — the site sets
// scroll-behavior: smooth on the root, so the two-arg scrollTo starts an
// animation that a pending transition can cancel, leaving the old offset.
var main = document.querySelector('main');
if (main) main.scrollTop = 0;
window.scrollTo(0, 0);
window.scrollTo({ top: 0, behavior: 'instant' });

// Belt and braces: if anything restores the departed page's offset after
// our reset, undo exactly that and nothing else.
var reassert = function() {
if (!window.location.hash && priorScroll !== 0 && window.scrollY === priorScroll) {
window.scrollTo({ top: 0, behavior: 'instant' });
}
};
setTimeout(reassert, 0);
setTimeout(reassert, 450);

// Re-init FastNav for any new links in the swapped content
initFastNav();
Expand Down Expand Up @@ -219,14 +244,37 @@
});
}

// The ClientRouter sets history.scrollRestoration = 'manual' and swallows
// hash scrolling on both direct loads and client-side navigations (same
// reason TableOfContents scrolls manually). Honor the URL hash ourselves,
// with the same fixed-header offset the TOC uses.
var HEADER_OFFSET = 80;
function scrollToHash() {
if (!window.location.hash) return;
var el = document.getElementById(decodeURIComponent(window.location.hash.slice(1)));
if (!el) return;
var align = function() {
var top = el.getBoundingClientRect().top + window.pageYOffset - HEADER_OFFSET;
window.scrollTo({ top: top, behavior: 'instant' });
};
align();
// During a view transition the first measurement can be off (layout still
// settling, or the router scrolling after us) — re-align once it quiets.
setTimeout(function() {
if (Math.abs(el.getBoundingClientRect().top - HEADER_OFFSET) > 4) align();
}, 350);
}

// Init
initFastNav();
scrollToHash();
// Prefetch after page settles
setTimeout(prefetchVisible, 1000);

// Re-init on Astro view transitions (fallback)
document.addEventListener('astro:page-load', function() {
initFastNav();
scrollToHash();
setTimeout(prefetchVisible, 500);
});
})();
Expand Down
218 changes: 157 additions & 61 deletions src/components/Sidebar.astro
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { tabNavigation, getActiveTab, getActiveGroup, type NavItem, type NavGrou
const currentPath = Astro.url.pathname;
const activeTab = getActiveTab(currentPath);
const isDocsTab = activeTab?.tab === 'Docs';
const isSdkTab = activeTab?.tab === 'SDK';

// For Docs tab: groups are sections you switch between
// For other tabs: groups are also switchable sections
Expand Down Expand Up @@ -99,86 +98,117 @@ function getIconPath(icon?: string): string {
return iconPaths[icon || 'default'] || iconPaths.default;
}

// ── Lifecycle-ordered product dropdown (Docs tab only) ──────────────────────
// Reorders the Docs section dropdown from alphabetical to agent-development-
// lifecycle order, grouped under lightweight phase headers. This is dropdown-only
// and does NOT reorder the `groups` array in navigation.ts, so the page list below
// still follows nav order. "Falcon AI" sits last before Administration; a "Reference"
// group links out to the Integrations/Guides/SDK/API tabs.
// The dropdown only renders when a tab exposes multiple groups (today that's the
// Docs tab); any other multi-group tab falls back to a single unlabelled list.
// ── Lifecycle-ordered product dropdown (per-tab config) ──────────────────────
// Reorders a tab's section dropdown from alphabetical to a lifecycle order,
// grouped under lightweight phase headers, per `tabDropdownConfig` below. This
// is dropdown-only and does NOT reorder the `groups` array in navigation.ts, so
// the page list below still follows nav order. Tabs without a config entry
// (any other multi-group tab) fall back to a single unlabelled list.
interface DropdownItem { title: string; icon: string; href: string; }
interface DropdownSection { phase: string; items: DropdownItem[]; }

// A group's landing link is its first LINKED page, however deep — the first
// item may be an unlinked sub-heading (e.g. Cookbooks' Use Cases group).
function firstHref(list: NavItem[]): string | undefined {
for (const it of list) {
if (it.href) return it.href;
if (it.items) {
const h = firstHref(it.items);
if (h) return h;
}
}
return undefined;
}

const groupLookup = new Map(
allGroups.map(g => [g.group, { icon: g.icon || 'default', href: g.items[0]?.href || '/docs' }])
allGroups.map(g => [g.group, { icon: g.icon || 'default', href: firstHref(g.items) || '/docs' }])
);
// Single source for turning a group title into a dropdown item.
const toItem = (title: string): DropdownItem => {
const g = groupLookup.get(title);
return { title, icon: g?.icon || 'default', href: g?.href || '/docs' };
};

const phasedOrder: { phase: string; titles: string[] }[] = [
{ phase: 'Start', titles: ['Get Started'] },
{ phase: 'Observe & diagnose', titles: ['Observability', 'Error Feed'] },
{ phase: 'Evaluate & measure', titles: ['Evaluation', 'Simulation', 'Dataset'] },
{ phase: 'Improve', titles: ['Optimization', 'Annotations'] },
{ phase: 'Build & connect', titles: ['Prompt', 'Prototype', 'Agent Playground', 'Knowledge Base', 'Agent Command Center', 'Protect'] },
{ phase: 'Assistant', titles: ['Falcon AI'] },
{ phase: 'Administration', titles: ['RBAC'] },
];

// Reference links live in their own top-level tabs; surface them at the bottom.
const referenceItems: DropdownItem[] = [
{ title: 'Integrations', icon: 'plug', href: '/docs/integrations' },
{ title: 'Guides', icon: 'book', href: '/docs/cookbook' },
{ title: 'SDK Reference', icon: 'code', href: '/docs/sdk' },
{ title: 'API Reference', icon: 'webhook', href: '/docs/api' },
];

// SDK tab: the same phase-grouped product dropdown as Docs, with the SDK's products.
const sdkPhasedOrder: { phase: string; titles: string[] }[] = [
{ phase: 'Start', titles: ['SDK Overview'] },
{ phase: 'Observe & diagnose', titles: ['traceAI'] },
{ phase: 'Evaluate & measure', titles: ['Evaluation', 'Simulation', 'Datasets'] },
{ phase: 'Improve', titles: ['Prompt Optimization', 'Annotation Queues'] },
{ phase: 'Build & connect', titles: ['Knowledge Base'] },
{ phase: 'Protect', titles: ['Protect'] },
];
const sdkReferenceItems: DropdownItem[] = [
{ title: 'Product Docs', icon: 'book', href: '/docs' },
{ title: 'Integrations', icon: 'plug', href: '/docs/integrations' },
{ title: 'Guides', icon: 'compass', href: '/docs/cookbook' },
{ title: 'API Reference', icon: 'webhook', href: '/docs/api' },
];
// Per-tab dropdown configuration. Adding a new tab's phase-grouped dropdown
// means adding an entry here — never adding another if/else branch below.
interface TabDropdownConfig {
phased: { phase: string; titles: string[] }[];
reference: DropdownItem[];
// Group titles to exclude from the "More" future-proofing leftovers (e.g. a
// group that's intentionally not surfaced in the dropdown at all).
hidden?: string[];
}

let dropdownSections: DropdownSection[];
if (isDocsTab) {
dropdownSections = phasedOrder
.map(p => ({ phase: p.phase, items: p.titles.filter(t => groupLookup.has(t)).map(toItem) }))
.filter(s => s.items.length > 0);
const tabDropdownConfig: Record<string, TabDropdownConfig> = {
Docs: {
phased: [
{ phase: 'Start', titles: ['Get Started'] },
{ phase: 'Observe & diagnose', titles: ['Observability', 'Error Feed'] },
{ phase: 'Evaluate & measure', titles: ['Evaluation', 'Simulation', 'Dataset'] },
{ phase: 'Improve', titles: ['Optimization', 'Annotations'] },
{ phase: 'Build & connect', titles: ['Prompt', 'Prototype', 'Agent Playground', 'Knowledge Base', 'Agent Command Center', 'Protect'] },
{ phase: 'Assistant', titles: ['Falcon AI'] },
{ phase: 'Administration', titles: ['RBAC'] },
],
// Reference links live in their own top-level tabs; surface them at the bottom.
reference: [
{ title: 'Integrations', icon: 'plug', href: '/docs/integrations' },
{ title: 'Cookbooks', icon: 'book', href: '/docs/cookbook' },
{ title: 'SDK Reference', icon: 'code', href: '/docs/sdk' },
{ title: 'API Reference', icon: 'webhook', href: '/docs/api' },
],
},
// SDK tab: the same phase-grouped product dropdown as Docs, with the SDK's products.
SDK: {
phased: [
{ phase: 'Start', titles: ['SDK Overview'] },
{ phase: 'Observe & diagnose', titles: ['traceAI'] },
{ phase: 'Evaluate & measure', titles: ['Evaluation', 'Simulation', 'Datasets'] },
{ phase: 'Improve', titles: ['Prompt Optimization', 'Annotation Queues'] },
{ phase: 'Build & connect', titles: ['Knowledge Base'] },
{ phase: 'Protect', titles: ['Protect'] },
],
reference: [
{ title: 'Product Docs', icon: 'book', href: '/docs' },
{ title: 'Integrations', icon: 'plug', href: '/docs/integrations' },
{ title: 'Cookbooks', icon: 'compass', href: '/docs/cookbook' },
{ title: 'API Reference', icon: 'webhook', href: '/docs/api' },
],
},
// Cookbooks tab: three top-level groups (Get Started, Use Cases, Platform).
Cookbooks: {
phased: [
{ phase: '', titles: ['Get Started', 'Use Cases', 'Platform'] },
],
reference: [
{ title: 'Product Docs', icon: 'book', href: '/docs' },
{ title: 'Integrations', icon: 'plug', href: '/docs/integrations' },
{ title: 'SDK Reference', icon: 'code', href: '/docs/sdk' },
{ title: 'API Reference', icon: 'webhook', href: '/docs/api' },
],
},
};

// Future-proofing: surface any Docs group not placed above under "More" so new
// products never silently vanish.
const placed = new Set<string>(phasedOrder.flatMap(p => p.titles));
const leftovers = allGroups.map(g => g.group).filter(t => !placed.has(t)).map(toItem);
if (leftovers.length) dropdownSections.push({ phase: 'More', items: leftovers });
const activeDropdownConfig = activeTab ? tabDropdownConfig[activeTab.tab] : undefined;

dropdownSections.push({ phase: 'Reference', items: referenceItems });
} else if (isSdkTab) {
dropdownSections = sdkPhasedOrder
let dropdownSections: DropdownSection[];
if (activeDropdownConfig) {
const { phased, reference, hidden = [] } = activeDropdownConfig;

dropdownSections = phased
.map(p => ({ phase: p.phase, items: p.titles.filter(t => groupLookup.has(t)).map(toItem) }))
.filter(s => s.items.length > 0);

// Future-proofing: any SDK group not placed above still shows under "More".
const placed = new Set<string>(sdkPhasedOrder.flatMap(p => p.titles));
// Future-proofing: surface any group not placed above (besides explicitly
// hidden ones) under "More" so new products never silently vanish.
const placed = new Set<string>([...phased.flatMap(p => p.titles), ...hidden]);
const leftovers = allGroups.map(g => g.group).filter(t => !placed.has(t)).map(toItem);
if (leftovers.length) dropdownSections.push({ phase: 'More', items: leftovers });

dropdownSections.push({ phase: 'Reference', items: sdkReferenceItems });
dropdownSections.push({ phase: 'Reference', items: reference });
} else {
// Other multi-group tabs: one unlabelled section (empty phase hides the header).
// Other multi-group tabs without a config: one unlabelled section (empty
// phase hides the header).
dropdownSections = [{ phase: '', items: allGroups.map(g => toItem(g.group)) }];
}

Expand Down Expand Up @@ -280,7 +310,73 @@ function inferApiMethod(title: string): { method: string; css: string } | null {

return (
<div>
{hasChildren ? (
{hasChildren && item.collapsible ? (
/* Collapsible top-level item-with-children — opt-in via item.collapsible */
<div class="mt-3 first:mt-0 sidebar-collapsible" data-sidebar-collapsible data-default-open={isItemOrChildActive(item) ? "true" : "false"}>
<button
type="button"
class:list={[
"w-full flex items-center gap-1 px-2 py-1.5 text-sm transition-colors rounded-md cursor-pointer",
isItemOrChildActive(item)
? "text-[var(--color-text-primary)] font-medium"
: "text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] hover:bg-[var(--color-bg-hover)]"
]}
data-collapsible-trigger
>
<svg
class:list={[
"w-3.5 h-3.5 flex-shrink-0 transition-transform duration-200",
isItemOrChildActive(item) ? "text-[var(--color-text-muted)]" : "text-[var(--color-text-muted)]"
]}
data-collapsible-chevron
fill="none" stroke="currentColor" viewBox="0 0 24 24"
>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
</svg>
<span class="flex-1 text-left">{item.title}</span>
</button>
<div class="pl-4 overflow-hidden" data-collapsible-content>
{item.items?.map((child) => {
const childActive = isItemActive(child.href);
const childMethod = isApiTab ? inferApiMethod(child.title) : null;
const childDisabled = !child.href;
const badgeEl = child.badge ? (
<span class:list={[
"px-1.5 py-0.5 text-[10px] font-medium rounded",
child.badge === 'New'
? "bg-[var(--color-success)]/10 text-[var(--color-success)]"
: "bg-[var(--color-bg-tertiary)] text-[var(--color-text-muted)]"
]}>{child.badge}</span>
) : null;
return childDisabled ? (
<div
class="flex items-center gap-1.5 px-2 py-1.5 text-sm rounded-md text-[var(--color-text-muted)] cursor-not-allowed select-none"
aria-disabled="true"
>
<span class="flex-1 truncate">{child.title}</span>
{badgeEl}
</div>
) : (
<a
href={child.href}
class:list={[
"flex items-center gap-1.5 px-2 py-1.5 text-sm transition-colors rounded-md",
childActive
? "text-[var(--color-text-primary)] bg-[var(--color-bg-hover)] font-medium"
: "text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] hover:bg-[var(--color-bg-hover)]"
]}
>
{childMethod && (
<span class={`api-method-badge ${childMethod.css}`}>{childMethod.method}</span>
)}
<span class="flex-1 truncate">{child.title}</span>
{badgeEl}
</a>
);
})}
</div>
</div>
) : hasChildren ? (
/* Item with children */
<div class="mt-3 first:mt-0">
{/* Sub-heading — always a label, never a link */}
Expand Down
Loading
Loading