Skip to main content
Features

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

February 23, 2026 Updated March 1, 2026

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:

  1. User types at least 2 characters
  2. Input triggers performSearch() with 300ms debounce
  3. Fetches /search-index.json (cached after first load)
  4. Filters against title, description, tags, and category (case-insensitive)
  5. 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, fedoralinux; docker, kubernetescontainers). 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
searchknowledge-graphtendril-graphcytoscapetagsvisualizationconfig