Search & Knowledge Graph
Search index generation, header search dropdown, configurable KnowledgeGraph visualization on /blog and /tags, MiniGraph on article pages, and admin settings for every visual parameter
Search & Knowledge Graph
ArgoBox has two content discovery systems: a text search dropdown in the header and an interactive knowledge graph that visualizes relationships between posts, tags, and categories.
Search Index
Generation
The search index is built as a dynamic Astro endpoint at src/pages/search-index.json.js. At build time it pulls from all content collections (posts, journal, projects, configurations), filters out drafts in production, and outputs a JSON array at /search-index.json.
Each entry contains:
{
"slug": "post-title-slug",
"title": "Post Title",
"description": "Short excerpt",
"pubDate": "2026-02-23T00:00:00Z",
"category": "DevOps",
"tags": ["docker", "kubernetes"],
"readTime": "5 min read",
"type": "post",
"url": "/posts/post-title-slug/"
}
The endpoint sets Cache-Control: max-age=3600 (1 hour).
Header Search
The search dropdown lives in src/components/Header.astro. Clicking the magnifying glass icon toggles a 300px-wide panel with a text input.
How search works:
- User types at least 2 characters
- Input triggers
performSearch()with 300ms debounce - Fetches
/search-index.json(cached after first load) - Filters against
title,description,tags, andcategory(case-insensitive) - Displays up to 8 results
Each result shows a type badge (Blog/Project/Config/Journal), title, truncated description, and the first 3 tags. Clicking navigates to the full content page.
KnowledgeGraph
The full-page interactive graph visualization at src/components/KnowledgeGraph.astro (~3,360 lines). Used on /blog and individual tag pages to show how content connects through shared tags and categories.
All visual parameters are configurable via the config prop (backed by TendrilGraphConfig). When no config is provided, sensible defaults are used. ArgoBox reads config overrides from Cloudflare KV via getGraphConfig().
Node Types
| Type | Default Color | Sizing |
|---|---|---|
| Posts | Blue (#3B82F6) | Scales by connection count (15–35px) |
| Tags | Green (#10B981) | Scales by connection count, 0.8x ratio |
| Categories | Purple (#8B5CF6) | Scales by connection count |
Category-specific colors are defined for 21 categories (Kubernetes, Docker, Infrastructure, DevOps, Homelab, Security, etc.). All colors are configurable via config.nodeColors.
Interaction
- Click a node: Show details panel with metadata, connected posts, and related tags. The clicked node turns bright blue (spotlight-primary), direct neighbors turn pale yellow (spotlight-neighbor), and connecting edges turn light blue. All other nodes dim to 25% opacity with readable slate-colored labels — visible but clearly not center stage. All spotlight colors and opacities are configurable via
config.spotlight. - Hover: Spotlight effect — highlights the node + its neighbors on top of the existing graph (no fading on hover, just additive highlighting)
- Click background: Clear selection and restore all nodes to full visibility
Controls
| Control | Function |
|---|---|
| Zoom +/- | Zoom in/out |
| Reset | Reset viewport |
| Physics toggle | Enable/disable live physics simulation |
| Filter buttons | All / Posts / Tags |
| Layout selector | Force, Concentric, Spiral, Radial, Clustered, Grid, Organic, Tree, plus Obsidian mode |
Container Layout
The graph renders in a configurable container. Default values:
.graph-container-wrapper {
width: var(--graph-width, min(90vw, 960px));
aspect-ratio: var(--graph-aspect, 1 / 1);
margin: 0 auto 2rem;
}
Container dimensions are injected as CSS custom properties from config.container (maxWidth, aspectRatio, widthExpression). On mobile, the expression scales it down while maintaining aspect ratio. Fullscreen mode overrides to 100vw x 100vh.
Physics Engine
Nine configurable parameters for the force-directed layout:
| Parameter | Default | Effect |
|---|---|---|
| Center Force | 0.12 | Pulls hub nodes toward center |
| Repel Force | 2000 | Pushes nodes apart (Coulomb repulsion) |
| Link Force | 0.2 | Edge spring strength (Hooke's law) |
| Link Distance | 160 | Target edge length in pixels |
| Damping | 0.82 | Velocity retention per frame |
| Enabled | true | Physics active on load |
| Bounding Box | false | Nodes expand freely (no viewport clamping) |
| Settle Threshold | 0.5 | Velocity threshold per node for calm detection |
| Settle Frames | 300 | ~5 seconds of calm before auto-fit and freeze |
Free Expansion & Delayed Settle
With boundingBox: false, nodes expand naturally during the first ~5 seconds as the physics simulation finds equilibrium. There is no viewport clamping — nodes can spread beyond the visible area temporarily.
Settle Detection & Physics Freeze
The engine monitors total kinetic energy per frame. When average velocity per node drops below settleThreshold for settleFrames consecutive frames (~5s at 60fps with the default 300), it auto-calls cy.fit() to zoom the camera so all nodes fill the container with 50px padding, then disables physics entirely. After this point, zooming and panning are pure camera operations — node positions stay frozen. The physics toggle in the control panel unchecks automatically via the onSettle callback. Users can re-enable physics manually via the toggle, which resets the settle cycle.
The Obsidian-style preset adds additional controls: Spine Length (tight connections for leaves), Bridge Length (loose connections for hubs), Node Scale, and Label Threshold.
Obsidian Startup Layout Handling
The Obsidian preset uses a consumer startup mode (obsidian-circle) rather than a native Tendril layout key. Startup now explicitly detects obsidian or obsidian-circle and routes to Obsidian activation logic instead of calling setLayout() with an unsupported key.
This prevents corner-loaded/tiny startup states and keeps initial framing centered and demo-ready.
Tag Hierarchy
The graph implements a taxonomy system where tags can be grouped hierarchically (e.g., gentoo, ubuntu, fedora → linux; docker, kubernetes → containers). About 40 hierarchical relationships are defined for ArgoBox. The hierarchy is configurable via config.behavior.tagHierarchy. Tags with only 1 connection are filtered out by default (configurable via config.behavior.filterSingleUseTags).
Built on @argobox/tendril-graph
The visualization uses the @argobox/tendril-graph package (packages/tendril-graph/), a framework-agnostic wrapper around Cytoscape.js. Key methods:
const graph = new TendrilGraph(selector, {
nodes, edges,
config: { physics: { repelForce: 2500 } }, // Optional config
onNodeClick,
onSettle
});
graph.setConfig({ physics: { damping: 0.9 } }); // Live config update
graph.setLayout('force');
graph.enablePhysics(true);
graph.zoom(1.2);
graph.reset();
graph.filterByType('post');
Admin Configuration
The graph-settings admin module (/admin/graph-settings) provides a full UI for every configurable parameter. See Tendril Knowledge Graph — Admin Integration for details.
Quick Adjust section at the top surfaces the 6 most impactful settings. Five detailed tabs cover Physics, Styling, Spotlight, Container, and Behavior. Changes auto-save with 500ms debounce to Cloudflare KV. Config takes effect on next deploy.
MiniGraph
A lightweight version of the knowledge graph embedded on individual article pages at src/components/MiniGraph.astro (~1,578 lines). Shows the current post's local neighborhood in the content graph.
Graph Building
The MiniGraph builds a 3-level neighborhood:
| Level | Content | Max Items |
|---|---|---|
| Level 0 | Current post | 1 |
| Level 1 | Posts sharing tags with current post + their tags | 6 posts |
| Level 2 | Posts sharing tags with Level 1 posts + their tags | Varies |
Related posts are scored by tag overlap count, then sorted by date. The getRelatedPosts() function in src/pages/posts/[slug].astro handles discovery, with fallback to recent posts if no tag matches exist.
Features
- Fullscreen mode: Toggle button expands the graph full-screen with a side-by-side layout — graph on left, info panel on right
- Content preview: Clicking a post node displays its metadata, tags, connected nodes, and a content excerpt
- Node navigation: Click through to the full article from the info panel
Edge Types
| Edge Type | Meaning |
|---|---|
post-tag |
Post connected to its own tags |
post-related |
Posts connected by shared tags |
tag-post |
Tag connected to posts that use it |
Script Architecture
KnowledgeGraph.astro uses a two-block script pattern because it needs both SSR data injection and ES module imports — which can't coexist in a single script tag.
Block 1 — <script is:inline define:vars>: Receives serialized config JSON from the Astro frontmatter via define:vars, parses it, and stores the result on window.GRAPH_CONFIG and window.GRAPH_DATA. This block runs as an inline script (no module bundling).
Block 2 — <script> (module): Imports TendrilGraph from @argobox/tendril-graph, reads config back from window.GRAPH_CONFIG, builds Cytoscape styles, processes graph data, and initializes the graph engine.
Astro Frontmatter (SSR)
│ cfg = deepMerge(DEFAULT_CONFIG, props.config)
│ Serialize each sub-config as JSON string
▼
<script is:inline define:vars={{ cfgPhysicsJSON, cfgBehaviorJSON, ... }}>
│ Parse JSON → window.GRAPH_CONFIG = { physics, behavior, ... }
│ Also: window.GRAPH_DATA, window.OBSIDIAN_THEME
▼
<script> (module)
│ import { TendrilGraph } from '@argobox/tendril-graph'
│ Read window.GRAPH_CONFIG → _cfgPhysics, _cfgBehavior, etc.
│ Build styles, process nodes/edges, init TendrilGraph
▼
Live graph in the browser
Variables declared in Block 1 (const _cfgBehavior = ...) are not visible in Block 2. All data must cross via window. See Troubleshooting: Script Scope for details.
Data Flow
Content Collections (posts, journal, projects, configs)
│
├──► /search-index.json ──► Header Search Dropdown
│
└──► Page Rendering
├──► /blog page ──► getGraphConfig() ──► KnowledgeGraph (full interactive)
├──► /tag/[tag] ──► getGraphConfig() ──► KnowledgeGraph (filtered to tag)
└──► /posts/[slug] ──► MiniGraph (local neighborhood)
Admin: /admin/graph-settings ──► /api/admin/graph-config ──► KV ──► next build
Key Files
| File | Purpose |
|---|---|
src/pages/search-index.json.js |
Search index generation |
src/components/Header.astro |
Search dropdown UI |
src/components/KnowledgeGraph.astro |
Full-page graph visualization |
src/components/MiniGraph.astro |
Per-article graph widget |
packages/tendril-graph/ |
Graph library (Cytoscape.js wrapper) |
packages/tendril-graph/src/config.js |
Config defaults, merge utility, style builder |
packages/tendril-graph/src/config.d.ts |
TypeScript type declarations |
src/lib/graph-config.ts |
KV storage for graph config |
src/pages/api/admin/graph-config.ts |
Admin config REST API |
src/pages/admin/graph-settings.astro |
Admin settings UI |
src/config/modules/graph-settings.ts |
Module manifest |
src/pages/tag/[tag].astro |
Tag page with filtered graph |
src/pages/blog/index.astro |
Blog page with full graph |