bitbaum Start a project
Package · MIT

bip-kit

Blog, roadmap and changelog from repo-authored markdown. Typed blocks, your design tokens on top.

Why it exists

Writing that lives in a CMS drifts from the product. Writing that is a pile of regexes drifts from itself. The studio writes in the repository, reviewed like code, and needed one dialect.

How it works

Markdown in, typed blocks out: headings, figures, a table of contents, reading time. A reference React renderer is optional. Bring your own tokens; the kit does not own your look. A chart fence that is malformed fails at build time rather than on the reader's screen.

Where it fits

It is how the studio builds in public. OrangeCat's articles, Evig, Botsmann, DataCat and the others render through it, so a claim on a page and a claim in a commit are the same text.

LicenceMIT
Registry releasev0.6.1
Install
Sourcebitbaum/bip-kit
Declared consumers17 repositories

API and examples

Documentation from source commit 53850fbd30a5 · 2026-11-06. Source documentation can include work beyond the registry release.

Read the package documentation

Release notes are tracked in CHANGELOG.md.

Canonical development records

loadDevelopmentProfile(mapUrl, slug) reads the selected public profile from a remote { projects: [...] } map. DevelopmentPage from @bitbaum/bip-kit/react renders its roadmap or changelog with the same responsive layout, source links and failure states on every product. Import @bitbaum/bip-kit/styles.css and override its CSS variables to match the host product. No product names or service hosts are stored in this package.

import { loadDevelopmentProfile } from "@bitbaum/bip-kit";
import { DevelopmentPage } from "@bitbaum/bip-kit/react";
import "@bitbaum/bip-kit/styles.css";

const profile = await loadDevelopmentProfile(publicMapUrl, projectSlug);
return <DevelopmentPage profile={profile} section="roadmap" profileHref={publicProfileUrl} />;

The producer is fetched with an eight-second timeout and no stored fallback. Unavailable data says unavailable; an empty history says empty. Private fields are excluded from the projection. Labels and navigation URLs are overridable for localisation; operator-authored canonical record text is left unchanged.

Linking the roadmap and the changelog

Put the same {#id} on a roadmap milestone and on the changelog line that delivered it:

<!-- ROADMAP.md -->
### Events, end to end {#events}
- [x] Pay the crew from the event {#crew-pay}

<!-- CHANGELOG.md -->
## 2026-10-07
- Organizers pay each crew member in one click {#crew-pay}

DevelopmentPage then links both ways: each milestone shows the dated changelog entries that delivered it (/changelog#change-2026-10-07), and each changelog line names the milestone it advanced (/roadmap#step-crew-pay). A goal's milestones are drawn as a trail, filled where done. The tokens are never printed. Ids are lowercase kebab-case starting with a letter, so (#1240) pull-request references are left alone; a line may cite a goal's id too.

For a custom page, linkDevelopment(profile) returns the same model: goals (steps, deliveredIn, first/last dates, percent, next step), changes (lines with what each advances), the journey (shipped / now / next / later), and two honesty checks — danglingRefs (ids the changelog cites that nothing declares) and unrecordedSteps (milestones ticked done that no changelog line cites). RoadmapTrail and ChangeAdvances from @bitbaum/bip-kit/react render the two halves inside any layout.

Locally authored typed records

When content lives in an app's typed RoadmapDoc / ChangelogEntry modules rather than the fleet's remote public map, render the shared structure without moving or flattening product-owned copy:

import { RoadmapDocument, ChangelogDocument } from "@bitbaum/bip-kit/react";
import "@bitbaum/bip-kit/styles.css";

<RoadmapDocument doc={roadmap} lang="de" headingLevel={2} />
<ChangelogDocument
  entries={changes}
  lang="de"
  formatDate={(iso) => new Intl.DateTimeFormat("de-CH", { dateStyle: "long", timeZone: "UTC" }).format(new Date(`${iso}T12:00:00Z`))}
  tagLabels={{ feature: "Neu", improvement: "Verbessert", fix: "Behoben", platform: "Plattform", breaking: "Änderung" }}
/>

The roadmap can set showHeader={false} when the page already renders its title and lede. essayHref resolves product-local paths, and essayLink lets the host use its router while the renderer still rejects unsafe schemes. The changelog retains an ISO dateTime when formatDate supplies localized text. Both components use bp-* CSS variables and accept className, so a host can map its semantic tokens without copying the markup.

Blog · Roadmap · Changelog · Q&A for product sites — one content contract, one reference renderer, instead of five blog stacks.

You want to build in public. What you don't want is a CMS, a markdown pipeline, three renderers, and a security review every time a product site needs a blog. bip-kit is the small, sharp core of that stack:

  • @bitbaum/bip-kit — a zero-dependency parser that turns repo-authored markdown into typed blocks (plus the types for a roadmap and a user-facing changelog).
  • @bitbaum/bip-kit/react — a reference renderer: RSC-first server components, tiny client islands, semantic bp-* classes, every color a CSS variable.
  • @bitbaum/bip-kit/styles.css — a complete neutral theme (measured ~70ch line length, real vertical rhythm, dark mode, mobile-first, print-safe) that you retheme var-by-var.

Same vocabulary everywhere, your design tokens on top.

npm i @bitbaum/bip-kit
# optional, only if your content uses them:
npm i shiki    # syntax highlighting
npm i katex    # math
npm i mermaid  # diagrams (client-side)

Core parser: zero dependencies. Renderer: react >= 18 as the only required peer; shiki/katex/mermaid are optional peers — everything degrades gracefully without them.

Quick start (Next.js App Router)

// app/blog/[slug]/page.tsx — a server component; ArticleBody is async (RSC)
import { readFileSync } from "node:fs";
import { parseFrontmatter, parseContentBlocks, extractToc, readingTime } from "@bitbaum/bip-kit";
import { ArticleBody, Toc, ReadingProgress } from "@bitbaum/bip-kit/react";
import "@bitbaum/bip-kit/styles.css";

export default async function Post({ params }: { params: Promise<{ slug: string }> }) {
  const { slug } = await params;
  const raw = readFileSync(`content/blog/${slug}.md`, "utf8");
  const { meta, body } = parseFrontmatter(raw);
  const blocks = parseContentBlocks(body);
  const toc = extractToc(blocks);
  const { minutes } = readingTime(blocks);

  return (
    <>
      <ReadingProgress />
      <h1>{meta.title}</h1>
      <p>{minutes} min read</p>
      <Toc items={toc} />
      <ArticleBody blocks={blocks} />
    </>
  );
}

Diagrams? Mermaid is client-side and heavyweight, so it lives on its own subpath — it never enters your bundle unless you ask:

import { MermaidBlock } from "@bitbaum/bip-kit/react/mermaid"; // requires the mermaid peer

<ArticleBody blocks={blocks} components={{ mermaid: MermaidBlock }} />;

Without the override, mermaid blocks render their source in a styled <pre> — honest degradation, never a broken build.

MermaidBlock follows your theme however you switch it: an explicit data-theme="light|dark" on <html> wins, then a light/dark class (next-themes with attribute="class", Tailwind's .dark), then the OS preference — and it re-renders live on any of them changing. Diagram colors resolve from your --bp-* tokens and are normalized to a form mermaid's color parser accepts, so tokens authored in oklch()/lab() (Tailwind v4 palettes) render correctly instead of blanking the diagram (fixed in 0.2.1).

The block vocabulary

One syntax example per block. Everything the v0.1 parser understood still parses identically; v0.2 adds the rest of what long-form writing needs.

Headings — h2 / h3 / h4

## Section        → { type: "h2", text, id: "section", spans }
### Subsection    → h3
#### Detail       → h4

Headings carry a slugified, de-duplicated id (## Über uns → ueber-uns) — anchors and TOC for free. Which slug you get is a policy you choose.

Paragraphs & inline markup — p

Body text with **bold**, *emphasis*, `code`, [links](https://example.com) and footnote refs[^1].

Every text-bearing block keeps its raw text AND carries spans: Inline[] — a parsed tree of text | strong | em | code | link | footnoteRef nodes. Consumers stop re-parsing **bold** themselves.

Emphasis follows CommonMark's flanking-delimiter rules (since v0.2.2), so what authors type is what every other markdown reader would show them:

MarkdownResultWhy
`\*not emphasis\*`literal `*not emphasis*`backslash escapes the full ASCII punctuation set
`` `a\*b` ``code `a\*b`code spans take no escapes — the backslash is content
`foo*bar*baz``foo`·_bar_·`baz``*` may open and close intraword
`snake_case_name`literal `snake_case_name``_` may **not** — identifiers survive untouched
`***bold italic***`em wrapping strongalso `___x___`, `**_x_**`, `_**x**_`

Anything that does not resolve — a dangling **, an unmatched _ — stays literal text, exactly as before. There is still no raw HTML and no markdown dependency: this is a hand-rolled scanner over one line.

Lists — ul / ol

- unordered item
1. ordered item

Items come with parallel itemSpans for inline markup.

Quotes — blockquote, pullquote

> An ordinary quote.

>> A big centered pull quote for the one sentence that matters.
>> — Attribution

The trailing — Name line (on a multi-line pull quote) becomes cite.

Callouts — callout (GitHub syntax, nestable)

> [!TIP] Optional custom title
> Callout body — full blocks, callouts can nest.

Kinds: [!NOTE] [!TIP] [!WARN]/[!WARNING] [!CAUTION]/[!DANGER] → note | tip | warn | danger.

![alt](/img/a.png)                     → image (v0.1, unchanged)
![alt](/img/a.png "A caption")         → figure (caption supports inline markup)
![one](/img/1.png)
![two](/img/2.png)                     → adjacent image lines merge into a gallery

A blank line between images keeps them separate.

Code — code

const x: number = 1;

The first word of the fence info is the language; the rest becomes a filename label. With the shiki peer, blocks are highlighted server-side once with both themes emitted as CSS variables (--shiki-light/--shiki-dark) — dark mode is pure CSS, zero client JS. Without shiki: clean mono fallback. Copy button included either way.

The filename label and the copy button share a toolbar row (.bp-codeblock-toolbar) above the code, in normal flow — since 0.2.7. Earlier versions overlaid the button on the <pre> and reserved a fixed band for it, which broke the moment a consumer's own CSS made buttons taller (a min-height: 2.75rem touch-target rule on coarse pointers, say). In flow, the button can be any height your stylesheet gives it and never covers code. On hover-capable pointers the button rests faded and reveals on hover/focus; on touch it is always visible.

Deploying with output: "standalone"? Because shiki is an optional peer, bip-kit's zero-config load goes through an import that bundlers (and Next.js output file tracing) cannot see — a standalone deploy would silently ship without shiki and lose highlighting. Register the loader once, at module scope (e.g. in your root layout), so the literal specifier lives in your code where the bundler and tracer can follow it:

import { setHighlighterLoader } from "@bitbaum/bip-kit/react";
setHighlighterLoader(() => import("shiki")); // one line; traceable; only if you use shiki

Or pass a loaded module per render: <ArticleBody blocks={blocks} highlighter={shiki} /> (with import * as shiki from "shiki"). Consumers without shiki write neither line — nothing to resolve, builds stay green.

Diagrams — mermaid

graph TD; A-->B

First-class block since v0.2 (v0.1 parsed these as code with lang mermaid — update your switch if you special-cased that).

Charts — chart

kind: bar title: Weekly signups ylabel: signups series Organic: Jan=12, Feb=30, Mar=41 series Paid: Jan=4, Feb=9

Or strict JSON: { "kind": "line", "series": [{ "name": "A", "points": [["W1", 10]] }] }. Kinds: bar | line | area. The renderer draws an inline, theme-aware SVG — axis, gridlines, legend, categorical palette from CSS vars, no chart library. A malformed spec throws at parse time with the reason — committed content is the trust boundary, and a broken chart should fail the build like a broken import, never vanish silently.

Math — math

$$e = mc^2$$

$$
\sum_{i=1}^n i = \frac{n(n+1)}{2}
$$

With the katex peer: server-rendered (import KaTeX's stylesheet in your app: import "katex/dist/katex.min.css"). Without it: the TeX source in a styled block with data-math — no crash.

Footnotes — footnote

A claim[^src] in running text.

[^src]: The definition, with **inline markup**.
  Continuation lines are indented.

Refs render as superscript links; definitions collect into a footnotes section at the end with backlinks.

Stats — stats

68 | essays shipped 99.9% | uptime

A row of value/label tiles.

The rest

---                          → hr
| A | B |                    → table (GFM, separator row required)
|---|---|
https://youtu.be/VIDEO_ID    → embed (lone URL on its own line, allowlisted)

Video embeds are allowlisted, never arbitrary: only YouTube/Vimeo URLs become embed blocks, rendered via youtube-nocookie.com / player.vimeo.com. Everything else stays a paragraph.

Frontmatter

---
title: My post
tags: [bitcoin, "build in public"]
authors:
  - Mao
  - George
---

parseFrontmatter is a key: value scanner, not a YAML parser. It is dependency-free on purpose and handles exactly three shapes: scalar key: value, key: [a, b] lists (also wrapped over several lines, the way prettier formats long ones, with commas allowed inside quoted items), and block - item lists. Every value comes back as a string or string[] — nothing else. The key ends at the first colon, so title: Bitcoin: a peer-to-peer system keeps its colon.

It will get real YAML wrong, quietly:

draft: true                             → "true"      ← the string, not a boolean
order: 3                                → "3"         ← the string, not a number
author:                                                ← nesting is not supported
  name: Mao

If your frontmatter needs booleans, numbers, dates, or nesting, use a real YAML parser — gray-matter is what kivvi keeps in front of bip-kit for exactly this — and hand the body it returns to parseContentBlocks:

import matter from "gray-matter";
const { data: meta, content: body } = matter(raw);
const blocks = parseContentBlocks(body);

bip-kit will not grow a YAML parser; one already exists.

A folder of posts — @bitbaum/bip-kit/node

Every product was writing the same fifty lines: list content/blog/*.md, read the frontmatter, sort newest first, 404 a bad slug. readCollection is that reader, and it accepts every shape those products used:

import { readCollection, readEntry } from "@bitbaum/bip-kit/node";

const posts = readCollection(join(process.cwd(), "content", "blog"));
const post = readEntry(join(process.cwd(), "content", "blog"), slug); // undefined → notFound()
// each: { slug, title, date, summary, tags, author, meta, body, blocks, toc, readingMinutes, file }
  • Slug from the file name; a 2026-09-30- prefix is the date and leaves the slug.
  • title: or the body's first # Heading; date: or publishedAt: or the

file name's date (YYYY-MM-DD, or the build fails naming the file); summary: / excerpt: / description: or the first paragraph.

  • draft: true or published: false stays out unless { includeDrafts: true }.
  • README.md and _*.md are ignored; readEntry only looks up plain slugs.

It lives on its own subpath because it reads the filesystem; the main entry stays isomorphic.

Author-flavoured markdown — normalizeMarkdown

The parser is strict on purpose. normalizeMarkdown is the one lenient step in front of it: */+ bullets become -, nested items flatten, a body # h1 becomes ##, single-quoted image captions become double-quoted, CRLF becomes LF, and code fences are left alone. readCollection and parseFaq apply it for you; call it yourself before parseContentBlocks for markdown from elsewhere (a database, a form).

Questions and answers — parseFaq + <Faq>

## Do I need an account?

No. Reading is open to anyone.

# Voting

## Who can vote?

Members. Safety decisions: people only, not AI agents.
import { parseFaq } from "@bitbaum/bip-kit";
import { Faq } from "@bitbaum/bip-kit/react";

const sections = parseFaq(readFileSync("content/faq.md", "utf8"));
return <Faq sections={sections} />;

# Heading starts a section (optional), ## Question starts a question, and everything until the next heading is its answer, in the full block vocabulary. <Faq> renders native <details> (no client JavaScript, keyboard- and screen-reader-friendly, found by the browser's search), gives every question a linkable id, and adds schema.org FAQPage data (structuredData={false} to leave it out). faqJsonLd(sections) and blocksToText(blocks) are exported for your own markup.

Helpers

extractToc(blocks);   // → { id, text, level }[]   (h2/h3/h4)
readingTime(blocks);  // → { words, minutes }      (200 wpm, min 1)
parseInline(text);    // → Inline[]                (the inline parser, standalone)
slugify("Über uns");  // → "ueber-uns"

Slug policy

A slug policy is your site's anchor contract. ## Das Geschäftsmodell becomes <h2 id="…">, and that id is the #anchor in every link anyone has ever shared to that section.

Because of that, bip-kit's default never moves. Two policies ship:

Policy`Geschäftsmodell` →`日本語` →For
`slugify` **(default)**`geschaeftsmodell``section`ASCII anchors; German transliteration (ä→ae, ß→ss)
`unicodeSlugify``geschäftsmodell``日本語`Multilingual sites keeping non-Latin anchors

Non-Latin headings have no ASCII spelling, so under the default they strip to nothing and fall back to section, section-2, … That is the reason unicodeSlugify exists.

Pass a policy through the slugify option — to both calls, since an id and the TOC link pointing at it are the same anchor:

import { parseContentBlocks, extractToc, unicodeSlugify } from "@bitbaum/bip-kit";

const options = { slugify: unicodeSlugify };
const blocks = parseContentBlocks(body, options);
const toc = extractToc(blocks, options); // same policy, or the two drift apart

Any SlugFn ((text: string) => string) works, so you can compose rather than reimplement — de-duplication (slug, slug-2, …) and the section fallback for empty results are applied on top of whatever you return:

import { slugify, type SlugFn } from "@bitbaum/bip-kit";

const prefixed: SlugFn = (text) => `sec-${slugify(text)}`;

The renderer — @bitbaum/bip-kit/react

Server components (RSC-first): ArticleBody (the one you usually need — an async server component that pre-awaits shiki/katex and emits a fully synchronous tree), plus Figure, Gallery, Callout, PullQuote, Stats, Footnotes, CodeBlock, Chart, TableBlock, VideoEmbed.

Client islands (each tiny, dependency-free): Toc (sticky scroll-spy; hides itself under 3 headings), ReadingProgress (top-of-page hairline), Lightbox (figure/gallery zoom, Escape to close), CopyButton. And MermaidBlock on @bitbaum/bip-kit/react/mermaid (theme colors resolved from your CSS vars at render time).

ArticleBody props: blocks, components ({ mermaid } override), highlighter (a loaded shiki module — see the code-block section), lightbox (default true), className.

Theming contract

@bitbaum/bip-kit/styles.css is a complete neutral theme; every visual decision is a CSS custom property you may override — light, and dark via @media (prefers-color-scheme) plus an explicit data-theme="dark" | "light" attribute on <html> that always wins.

VariableRole
`--bp-font-sans` / `--bp-font-mono`Type stacks
`--bp-size`Base font size (17px mobile / 18px ≥720px)
`--bp-leading`Body line-height (1.7)
`--bp-measure`Line length (70ch)
`--bp-flow`Vertical rhythm unit (1.5rem)
`--bp-radius` / `--bp-radius-sm`Corner radii
`--bp-bg` / `--bp-surface`Page & raised-surface backgrounds
`--bp-fg` / `--bp-fg-muted` / `--bp-fg-faint`Text hierarchy
`--bp-border`Hairlines & frames
`--bp-accent` / `--bp-accent-contrast`Links, active TOC, progress bar
`--bp-note` / `--bp-note-bg`Note callout
`--bp-tip` / `--bp-tip-bg`Tip callout
`--bp-warn` / `--bp-warn-bg`Warning callout
`--bp-danger` / `--bp-danger-bg`Danger callout
`--bp-chart-1` … `--bp-chart-6`Categorical chart palette

Retheme by redefining vars on :root (and your dark scope) — zero rule overrides needed. Components emit semantic classes only (bp-p, bp-h2, bp-callout bp-callout--warn, bp-figure, …), so a from-scratch stylesheet is equally supported.

Feedback — let people answer the roadmap and the changelog

In bitbaum products, a vote goes through Solon, not this module. Voting

on a roadmap or having a say in its direction is a right someone holds: an

OrangeCat account and a seat in the product's organisation on

Solon. General "this is broken" feedback on any

page goes through the Loki feedback widget. This module collects anonymous

input under a browser key. Use it only where anonymous input is what the

product actually wants.

Readers can mark a roadmap item needed or not needed, comment on a changelog entry, and suggest what is missing. As they type a suggestion, they see similar ones already on the list and can back one of those instead. No sign-in and no language model. bip-kit supplies the rules and the UI; your product supplies storage.

// app/api/feedback/route.ts
import { createFeedbackHandler, roadmapItemId, changelogEntryId } from "@bitbaum/bip-kit";
import { ROADMAP, CHANGELOG } from "@/lib/config";
import { store } from "@/lib/feedback-store"; // your FeedbackStore over your DB

const ids = new Set([
  ...ROADMAP.buckets.flatMap((b) => b.items.map(roadmapItemId)),
  ...CHANGELOG.map(changelogEntryId),
]);

export const { GET, POST } = createFeedbackHandler({
  store,
  isTarget: (id) => ids.has(id),
  allow: (request, action) => myRateLimit(request, action), // e.g. limitkit
});
// on the roadmap page (client components; wrap once, place anywhere)
import { FeedbackProvider, StanceButtons, CommentThread, SuggestBox } from "@bitbaum/bip-kit/react";

<FeedbackProvider endpoint="/api/feedback" targetIds={ids} labels={t.feedback}>
  {items.map((item) => (
    <li key={item.title}>
      {item.title}
      <StanceButtons targetId={roadmapItemId(item)} />
      <CommentThread targetId={roadmapItemId(item)} />
    </li>
  ))}
  <SuggestBox />
</FeedbackProvider>;
  • Give translated items an id. Without one, each item is keyed by the

slug of its title, so each language gets its own tally, and renaming an item resets its votes.

  • Voters are a random key the browser keeps (x-bip-voter). That makes

"one vote per person" true for honest readers; the allow hook (rate limiting per IP) is what stops someone who games it.

  • What gets refused (screenText): empty or 1–2 characters, over 1000

characters, more than one link, and floods. Criticism is not refused.

  • Store contract: one stance per voter per target (a second replaces the

first, null withdraws it) and one support per voter per suggestion. The tests in test/feedback.test.js run against memoryStore() and describe the behaviour your store must match.

Security model

Typed blocks are the security model:

  • No raw HTML, ever. Markdown becomes a discriminated union; the renderer emits React elements from typed data. There is no HTML passthrough for content to hide in — which makes the renderer safe for database/user-submitted content too, not just committed files.
  • Link hrefs are guarded at render. javascript: and every unknown scheme degrade to plain text (safeHref allows http/https/mailto/tel/relative/#).
  • Embeds are allowlisted. Only YouTube/Vimeo, always via the privacy player. A markdown file can never inject an arbitrary iframe.
  • The only dangerouslySetInnerHTML sinks are shiki/KaTeX output generated from escaped source — never author-supplied markup.
  • Parse errors in chart/stats fences throw instead of guessing.

Types you'll actually use

  • ContentBlock — the discriminated union your renderer switches on
  • Inline — the inline span union (text | strong | em | code | link | footnoteRef)
  • ChartSpec / ChartSeries — the tiny declarative chart contract
  • TocEntry, ReadingTimeResult
  • BlogPostMeta — minimal post frontmatter (slug, title, summary, tags, …)
  • RoadmapDoc / RoadmapBucket / RoadmapItem — a renderable roadmap
  • ChangelogEntry / ChangelogTag — user-facing changelog entries
  • ReleaseEntry — desktop/installer release notes

Starter routes

templates/next-app has copy-paste Next.js route stubs for /blog, /roadmap, and /changelog. They're intentionally thin — copy them into your app and style with your own tokens.

Company vs users

This kit is for product/company building-in-public. User-generated blogs belong on a social layer — don't bolt a UGC CMS onto every product domain.

Used in production

Extracted from, and dogfooded by, Loki (its Thoughts/blog, roadmap, and changelog) and AOZ Wohnen before it was ever a package.

License

MIT


Part of bitbaum — AI-native products on open infrastructure, built in Zürich. Every package here lists the apps that use it: which apps use bip-kit.

Changelog and releases

Read the repository changelog

Renamed — bip-kit is now @bitbaum/bip-kit

Same code, same version, same repository; the package now lives under the @bitbaum npm organization like the rest of the fleet's packages. Install @bitbaum/bip-kit and import from it (bip-kit stays installable and is deprecated once no adopter uses it). No API changed.

0.6.1 — 2026-10-26

  • Add RoadmapDocument and ChangelogDocument to bip-kit/react for rendering

locally authored RoadmapDoc and ChangelogEntry records with the same responsive semantic structure across products. The components keep product copy and date formatting caller-owned, accept localized category labels, expose token-based styles, preserve stable item IDs, and reject unsafe essay links. Roadmap rendering can defer its title to an existing page header.

0.6.0 — 2026-10-07

The roadmap and the changelog, read as one record.

  • A {#id} token on a roadmap milestone (or goal) and on a changelog line

links them. DevelopmentPage shows, under each milestone, the dated changelog entries that delivered it, and under each changelog line, the milestone it advanced — both as anchored links (#step-<id>, #goal-<id>, #change-<date>). Tokens are stripped from what is shown.

  • The roadmap is drawn as a trail per goal: one mark per milestone, filled

where done. Done marks keep aria-label="Done".

  • linkDevelopment(profile) exposes the model for custom pages (goals with

deliveredIn and first/last dates, changes with advances, the journey shipped/now/next/later, danglingRefs, unrecordedSteps); readRefs, phaseOf, changeAnchor alongside. RoadmapTrail, ChangeAdvances and linkedText from bip-kit/react.

  • Nothing upstream changes: the token rides inside the milestone titles and

changelog lines the fleet map already carries.

0.5.2 — 2026-09-30

  • normalizeMarkdown (and so readCollection and parseFaq) joins a wrapped

list item's indented lines into the item, the way prettier and most changelogs wrap long bullets. Before, the rest of the sentence rendered as a paragraph of its own. Indented numbered items are flattened like - ones.

0.5.1 — 2026-09-30

  • parseFrontmatter (and so readCollection) reads a […] list that wraps

over several lines, the form prettier gives long tag lists, and keeps commas inside quoted items. Before, a wrapped list silently came back empty.

0.5.0 — 2026-09-30

The parts every product was still writing for itself.

  • bip-kit/node: readCollection(dir) / readEntry(dir, slug) read a folder

of markdown files as a list of entries, newest first. Accepts title: or a body # h1; date:, publishedAt: or a YYYY-MM-DD- file name; summary:, excerpt:, description: or the first paragraph; draft: true / published: false. A malformed date fails the build, naming the file. Replaces five per-product readers.

  • normalizeMarkdown: the lenient step in front of the strict parser (bullets,

nested items, body h1, single-quoted captions, CRLF). Replaces four copies.

  • parseFaq + <Faq> (bip-kit/react): questions and answers from one

markdown file, rendered as native <details> with linkable ids and schema.org FAQPage data. faqJsonLd, blocksToText exported. Styles in styles.css (bp-faq-*), tokens only.

0.4.0 — 2026-09-25

Roadmaps and changelogs people can answer.

  • createFeedbackHandler — Web-standard GET/POST for votes ("needed" /

"not needed"), comments and suggestions; a Next.js route re-exports it.

  • FeedbackStore — the persistence contract a product implements over its own

database; memoryStore() is the reference.

  • Shared rules: screenText (length, links, floods), a honeypot, one stance

per voter per item, findSimilar duplicate detection for suggestions (word stems, no model, any script), wilson ranking.

  • bip-kit/react: FeedbackProvider, StanceButtons, CommentThread,

SuggestBox; every word is a prop (FeedbackLabels). Styles in styles.css, tokens only.

  • RoadmapItem.id and ChangelogEntry.id (optional): give them to a

translated roadmap so every language shares one tally.

0.3.1 — 2026-09-23

  • Fix loadDevelopmentProfile for Loki fleet-map roadmap milestones, preserving

completion state and source links while continuing to accept legacy strings.

  • Render checked milestones and provenance links in DevelopmentPage.
  1. v0.6.1
    Release notes

    What's Changed

    * chore(deps-dev): Bump the minor-and-patch group across 1 directory with 5 updates by @dependabot[bot] in https://github.com/bitbaum/bip-kit/pull/41 * chore: add the fleet package template files this repo is missing (git-release.yml, release.yml) by @catomean in https://github.com/bitbaum/bip-kit/pull/48 * feat: render typed roadmap and changelog records by @catomean in https://github.com/bitbaum/bip-kit/pull/49

    Full Changelog: https://github.com/bitbaum/bip-kit/compare/v0.6.0...v0.6.1

Recent source changes

Repository activity can include unreleased work; it is not a list of deployed changes.

  1. ci(publish): the current Publish workflow, which can make a package's first npm release (#59)
  2. feat: publish as @bitbaum/bip-kit (same code, same version) (#58)
  3. docs(agents): the org rules block, generated from bitbaum/fleet (#57)
  4. docs(agents): the org rules block, generated from bitbaum/fleet (#56)
  5. fix(release): apply verified build-output staging contract (#55)
  6. chore(deps): dependabot leaves TypeScript 7 alone until typescript-eslint supports it (#54)
  7. docs(agents): the org rules block, generated from bitbaum/fleet (#53)
  8. fix(security): refresh sanitizer and brace parser patches (#52)
All releases · All tags

Where it is declared

Linked products declare this package in their manifests. A declaration alone does not verify every feature, version or deployed integration.

Sources and verification

Install commands and declared consumers come from Fleet’s generated registry. Documentation and release notes come from the package repository. The catalogue does not certify production journey coverage.

Documentation from source commit 53850fbd30a5 · 2026-11-06. Source documentation can include work beyond the registry release.

Package verification runs