Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@weave-md/stream

Package Overview
Dependencies
Maintainers
1
Versions
1
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@weave-md/stream

Lightweight utilities for parsing and rendering streamed Weave content

latest
Source
npmnpm
Version
0.5.0
Version published
Maintainers
1
Created
Source

@weave-md/stream

Lightweight utilities for parsing and rendering streamed Weave content.

Why This Exists

Weave's node: links parse as standard markdown—but chat interfaces need to render them. This package handles that:

[see details](node:details?display=overlay)

When an LLM generates Weave content in a chat interface, the host app needs to:

  • Parse these node: URLs to understand what's being referenced
  • Extract sections from the streamed output (which may contain multiple stacked sections)
  • Render the links appropriately

This package provides zero-dependency utilities to do exactly that.

React is an optional peer dependency (only needed for the React component).

How It Differs from @weave-md/parse

@weave-md/stream@weave-md/parse
PurposeChat interface embeddingFull Weave tooling
InputSingle stream with stacked sectionsOne file per section
Output{ id, title, content }[]Full AST with positions, diagnostics
ValidationNone (trusts LLM output)Strict (errors on missing id, duplicates)
DependenciesZerounified, remark, micromark, etc.

This package provides a lightweight alternative that supports stacked sections in a single stream—ideal for LLM output.

Installation

npm install @weave-md/stream

Usage

Parse Node URLs

import { parseNodeUrl } from '@weave-md/stream'

const parsed = parseNodeUrl('node:intro?display=overlay')
// { id: 'intro', display: 'overlay' }

// Returns null for non-node URLs
parseNodeUrl('https://example.com') // null

Extract Sections from LLM Output

import { splitSections } from '@weave-md/stream'

const markdown = `
---
id: intro
title: Introduction
---

This is the intro. See [details](node:details).

---
id: details
title: Details
---

Here are the details.
`

const sections = splitSections(markdown)
// [
//   { id: 'intro', title: 'Introduction', content: 'This is the intro...' },
//   { id: 'details', title: 'Details', content: 'Here are the details.' }
// ]

The content field contains the raw markdown body with frontmatter stripped.

Streaming behavior: When called on incomplete streamed content:

  • Returns all complete sections found so far
  • The last section's content may be incomplete (still being streamed)
  • Re-call splitSections() as more content arrives

Graceful degradation: Node links render immediately as styled text, even before their target section exists. Once the target section is parsed, the link becomes interactive (clickable, shows preview, etc.).

React Integration (ChatGPT)

import { parseNodeUrl, WeaveLink, createDisplayConfig } from '@weave-md/stream/react'
import ReactMarkdown from 'react-markdown'

// Configure display fallbacks for narrow viewports
const displayConfig = createDisplayConfig({
  supported: ['inline', 'overlay', 'footnote'],
  fallbacks: {
    sidenote: 'footnote',
    margin: 'footnote',
    panel: 'overlay',
    stretch: 'overlay',
  }
})

function ChatMessage({ content, sections }) {
  return (
    <ReactMarkdown
      components={{
        a: ({ href, children }) => {
          const parsed = parseNodeUrl(href)
          if (parsed) {
            return (
              <WeaveLink
                {...parsed}
                resolveSection={(id) => sections.find(s => s.id === id)}
                displayConfig={displayConfig}
                onClick={(id, display) => handleNodeClick(id, display)}
              >
                {children}
              </WeaveLink>
            )
          }
          return <a href={href}>{children}</a>
        }
      }}
    >
      {content}
    </ReactMarkdown>
  )
}

Or render your own way:

a: ({ href, children }) => {
  const parsed = parseNodeUrl(href)
  if (parsed) {
    return <span className="weave-link" data-target={parsed.id}>{children}</span>
  }
  return <a href={href}>{children}</a>
}

markdown-it Integration (Cursor)

import MarkdownIt from 'markdown-it'
import { weaveLinkPlugin } from '@weave-md/stream/markdown-it'

const md = new MarkdownIt()
md.use(weaveLinkPlugin)

md.render('[see intro](node:intro?display=overlay)')
// <span class="weave-link" data-target="intro" data-display="overlay">see intro</span>

API Reference

parseNodeUrl(href: string): ParsedNodeUrl | null

Parses a node: URL and extracts its components.

interface ParsedNodeUrl {
  id: string
  display?: 'inline' | 'overlay' | 'footnote' | 'sidenote' | 'margin' | 'stretch' | 'panel'
  export?: 'appendix' | 'inline' | 'omit'
}

Returns null if the URL doesn't start with node:.

splitSections(markdown: string): Section[]

Extracts sections from markdown with stacked frontmatter.

interface Section {
  id: string
  title?: string
  peek?: string
  content: string  // Raw markdown body (frontmatter stripped)
}

Heuristic: A new section starts when --- appears after a blank line, followed by a line starting with id:.

createDisplayConfig(options): DisplayConfig

Configures which display types are supported and their fallbacks.

type DisplayType = 'inline' | 'overlay' | 'footnote' | 'sidenote' | 'margin' | 'stretch' | 'panel'

interface DisplayConfigOptions {
  // Which display types this environment supports
  supported?: DisplayType[]
  // Fallback for each unsupported type (defaults to 'overlay')
  fallbacks?: Partial<Record<DisplayType, DisplayType>>
}

// Returns resolved display type given the requested type
interface DisplayConfig {
  resolve: (requested: DisplayType | undefined) => DisplayType
}

Example: Narrow viewport config

import { createDisplayConfig } from '@weave-md/stream'

const narrowConfig = createDisplayConfig({
  supported: ['inline', 'overlay', 'footnote'],
  fallbacks: {
    sidenote: 'footnote',  // sidenote → footnote
    margin: 'footnote',    // margin → footnote  
    panel: 'overlay',      // panel → overlay
    stretch: 'overlay',    // stretch → overlay
  }
})

narrowConfig.resolve('sidenote')  // → 'footnote'
narrowConfig.resolve('overlay')   // → 'overlay'
narrowConfig.resolve(undefined)   // → 'inline' (default)

Example: Wide viewport config

const wideConfig = createDisplayConfig({
  supported: ['inline', 'overlay', 'footnote', 'sidenote', 'margin', 'panel'],
  fallbacks: {
    stretch: 'panel',  // stretch not supported, use panel
  }
})

Example: Responsive config with React

function useDisplayConfig() {
  const isNarrow = useMediaQuery('(max-width: 768px)')
  
  return useMemo(() => createDisplayConfig(
    isNarrow
      ? { supported: ['inline', 'overlay', 'footnote'], fallbacks: { sidenote: 'footnote', margin: 'footnote', panel: 'overlay' } }
      : { supported: ['inline', 'overlay', 'footnote', 'sidenote', 'margin', 'panel'] }
  ), [isNarrow])
}
interface WeaveLinkProps {
  id: string
  display?: string
  children: ReactNode
  resolveSection?: (id: string) => { title?: string; peek?: string } | null
  displayConfig?: DisplayConfig  // Resolves display type with fallbacks
  onClick?: (id: string, display: DisplayType) => void  // Only fires when resolved
}

The component automatically determines resolved state from resolveSection:

  • If resolveSection returns a section object → resolved, interactive
  • If resolveSection returns null or is not provided → pending, non-interactive

When displayConfig is provided, the effective display type is resolved before rendering and passed to onClick.

weaveLinkPlugin (markdown-it plugin)

Transforms node: links into styled spans with data attributes.

Display Components

Minimal React components for rendering node link content:

import { 
  Overlay, 
  Panel, 
  Footnotes, 
  FootnoteRef, 
  InlineExpand 
} from '@weave-md/stream/react'

Overlay

Floating modal panel for content.

const triggerRef = useRef<HTMLSpanElement>(null)

<Overlay open={isOpen} onClose={() => setOpen(false)} triggerRef={triggerRef}>
  <Markdown>{section.content}</Markdown>
</Overlay>

Panel

Slide-in side panel (like VS Code peek).

<Panel open={isOpen} onClose={() => setOpen(false)} title="Details" position="right">
  <Markdown>{section.content}</Markdown>
</Panel>

Footnotes & FootnoteRef

Collected footnotes at bottom with inline references.

// Inline reference
<FootnoteRef id="intro" number={1} text="see details" onClick={() => scrollTo('#fn-intro')} />

// Footnotes section
<Footnotes footnotes={[
  { id: 'intro', number: 1, title: 'Introduction', content: <Markdown>...</Markdown> }
]} />

InlineExpand

Expandable inline content.

<InlineExpand 
  expanded={isExpanded} 
  onToggle={() => setExpanded(!isExpanded)}
  trigger="See more"
>
  <Markdown>{section.content}</Markdown>
</InlineExpand>

These are minimal implementations for chat interfaces.

Component Behavior

In chat contexts, node links can't navigate to a separate page. The components:

  • Render as styled inline elements (not hyperlinks)
  • Display the link text with optional visual indicator
  • Include data-target and data-display attributes for custom handling
  • Accept an optional resolveSection callback for apps that can show previews

Streaming-safe rendering: During streaming, node links may reference sections that don't exist yet. The component renders gracefully in both states:

  • Before section exists: Renders as styled text (non-interactive, no click handler)
  • After section exists: Becomes interactive (clickable, shows preview on hover, etc.)

This prevents broken UI during streaming while enabling full interactivity once content is complete.

The host app controls what happens on click (show overlay, scroll to section, etc.).

Styling & Theming

Quick Start

For simple use cases, import the static CSS:

import '@weave-md/stream/styles.css'

Programmatic Theming (Mermaid-style)

For full control, use the theming API similar to Mermaid's approach:

import { getStyles, injectStyles } from '@weave-md/stream/react'

// Inject styles into document head
injectStyles({
  theme: 'default',  // 'default' | 'dark' | 'none'
  themeVariables: {
    linkColor: '#ff6600',
    linkHoverBg: 'rgba(255, 102, 0, 0.1)',
  },
  themeCSS: '.weave-link { font-weight: 500; }'
})

Or get the CSS string to inject yourself:

const css = getStyles({
  themeVariables: { linkColor: '#ff6600' }
})
// Insert into <style> tag, Shadow DOM, etc.

Theme Variables

interface ThemeVariables {
  linkColor?: string        // Link text color
  linkHoverColor?: string   // Link text color on hover
  linkHoverBg?: string      // Background color on hover
  linkFocusColor?: string   // Focus outline color
  linkActiveBg?: string     // Background when active/pressed
  pendingOpacity?: string   // Opacity for unresolved links
  fontFamily?: string       // Font family for link text
}

Predefined Themes

ThemeDescription
defaultBlue links, light backgrounds
darkLight blue links, dark-friendly backgrounds
noneNo base styles, only CSS variables

Custom CSS Injection

Append raw CSS after generated styles:

injectStyles({
  themeCSS: `
    .weave-link[data-display="overlay"]::after {
      content: " 🔗";
    }
  `
})

Scoped Styles (Shadow DOM / Isolation)

import { getScopedStyles } from '@weave-md/stream/react'

const css = getScopedStyles('.my-chat-container', {
  themeVariables: { linkColor: '#ff6600' }
})
// Generates: .my-chat-container .weave-link { ... }

Data Attributes

The components use the class weave-link with data attributes:

  • data-target — The target section ID
  • data-display — The display mode (if specified)
  • data-resolved — Whether the target section exists ("true" or "false")

Manual CSS Example

If you prefer writing your own CSS:

.weave-link {
  color: var(--weave-link-color, #0066cc);
  text-decoration: underline;
  text-decoration-style: dotted;
}

.weave-link[data-resolved="true"] {
  cursor: pointer;
}

.weave-link[data-resolved="true"]:hover {
  background-color: var(--weave-link-hover-bg, rgba(0, 102, 204, 0.1));
}

.weave-link[data-resolved="false"] {
  cursor: default;
  opacity: var(--weave-link-pending-opacity, 0.7);
}

Guidance for LLM Prompting

Important: This package adapts Weave content for constrained environments—it should NOT influence how LLMs write Weave.

LLMs should write full Weave format using any display type that makes semantic sense:

[see details](node:details?display=sidenote)  <!-- Use sidenote if it fits the content -->

The host app then adapts via displayConfig:

  • Wide viewport: renders as sidenote
  • Narrow viewport: falls back to footnote

Include this (or similar) when prompting LLMs to generate node links:

When writing node links, use any display type that fits the content semantically:
- inline, overlay, sidenote, margin, panel, footnote, stretch
- The host environment will adapt display types as needed for its viewport
- Do not self-limit to what you think the chat interface supports

This ensures LLMs write semantically appropriate node links while hosts handle runtime adaptation.

Note: For guidance on the full Weave format (:math[...], media blocks, etc.), see the main Weave documentation. This package only handles node links.

Weave Format Features (Not Included)

This package only handles node links. Other Weave format features are separate:

FeatureSolution
:math[...]Use remark-math-inline
:sub[A]{B}Use remark-substitute
```math blocksHandle in your code component
Media blocksHandle in your code component
GFMUse remark-gfm

Recommended: Add remark-math-inline and remark-substitute to your markdown pipeline so LLMs can stream full Weave syntax without the parser getting confused by unfamiliar constructs.

License

MIT

Keywords

weave-md

FAQs

Package last updated on 08 Feb 2026

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts