npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@elumixor/react-telegram

v0.2.2

Published

Stream React JSX into editable Telegram messages. Built on @elumixor/react-message-renderer + grammy.

Readme

@elumixor/react-telegram

Stream React JSX into editable Telegram messages. Built on @elumixor/react-message-renderer and grammy.

A <Message> is a Telegram message. Render once, the bot sends. Re-render with new state, the bot edits the same message in place via Telegram's editMessageText API. The reconciler tracks message ids and chunks for you.

Demo: parallel-research example

Demo: the parallel-research example — four messages updating concurrently, real Claude streaming into the reasoning panel, photo gallery, and a final JSON report. (higher-quality .mp4)

Install

bun add @elumixor/react-telegram @elumixor/react-message-renderer grammy react

Peer dependencies: grammy >= 1, react >= 19.

The 30-second tour

import { Message, useFinishRender } from "@elumixor/react-message-renderer";
import { TelegramRenderer } from "@elumixor/react-telegram";
import { Bot, type Context } from "grammy";
import { useEffect, useState } from "react";

function Agent() {
  const [steps, setSteps] = useState<string[]>([]);
  const finish = useFinishRender();

  useEffect(() => {
    void (async () => {
      for (const step of ["Loading…", "Analyzing…", "Done"]) {
        await new Promise((r) => setTimeout(r, 700));
        setSteps((p) => [...p, step]);
      }
      void finish();
    })();
  }, [finish]);

  return (
    <Message>
      <b>Working</b>
      <br />
      {steps.join("\n")}
    </Message>
  );
}

const bot = new Bot(process.env.BOT_TOKEN!);
bot.command("start", async (ctx: Context) => {
  const renderer = new TelegramRenderer(ctx, { throttleMs: 600 });
  await renderer.render(<Agent />);
});
await bot.start();

In the chat, the user sees one message that grows in place from "Loading…" to a full three-line summary. No tracking message ids manually.

What you get

  • TelegramRenderer — extend-free, pass a grammy Context and you're done.
  • <Message repliesTo={id}> — sets reply_parameters.message_id on first send.
  • <Message linkPreview={…}> — fine-grained link_preview_options: ignore certain URLs, force a specific URL, or disable.
  • Markdown-flavoured text inside <Message> is converted to Telegram-HTML automatically (**bold**, _italic_, [link](url), fenced code blocks, lists, headers, strikethrough).
  • Messages longer than 4000 chars are split across multiple consecutive Telegram messages, threaded with reply_parameters so they stay grouped.
  • When the React tree shrinks (<Message> removed, or text gets shorter than chunk count), orphan messages are deleted. When it grows, only new chunks get sent.

Public API

  • TelegramRenderer(ctx, { throttleMs?, logger? })
  • TelegramMessageManager(ctx, { replyToMessageId?, logger? }) — single-message-instance manager, if you want to skip the React layer
  • <Photo src caption?> and <Document src filename? caption?> — declarative attachments. Place them inside <Message>; they're sent as replies to the parent text message and tracked positionally so re-renders don't resend.
  • <Message threadId={n}> — explicit forum/topic thread routing. Overrides ctx.message?.message_thread_id.
  • serializeTelegram(node, mode?) — markdown-aware HTML serializer
  • markdownToTelegramHtml(text) — standalone Markdown → Telegram-HTML
  • splitMessage(text, maxLength?) — chunk safely on paragraph/sentence/word boundaries, preserving fenced code blocks

Tuning the throttle

throttleMs (default 800) controls how often the renderer is allowed to commit to Telegram while React is producing updates. Pick it to match what you're optimizing for:

| value | behaviour | when to use | | --- | --- | --- | | 300–500ms | Smoother streaming, more editMessageText calls. | Short streams (a few seconds) where you want a near-real-time feel. | | 800ms (default) | Balanced. Stays well under Telegram's per-chat edit budget on a single bot, even under sustained streaming. | Most agent / streaming-LLM use cases. | | 1500ms+ | Fewer edits, more "summary frames". | Long-running jobs where you'd rather show milestone updates than every token. |

The hard ceiling is Telegram's rate limit: roughly 1 message edit per second per chat (loosely; Telegram throttles silently when over). On a single bot serving multiple chats simultaneously you have separate budgets per chat, but if you're streaming into the same chat from concurrent jobs, lower throttles bunch them up against the shared limit.

The renderer always flushes on finish (useFinishRender()), so the final render lands regardless of throttleMs. You're trading intermediate-frame frequency for API budget — never the final state.

Examples

All under examples/cd in, cp .env.example .env, fill in your bot token, bun install && bun run dev.

  • streaming-counter — minimal: a fake progress bar streaming into one editable message. Start here.
  • streaming-llm — pipes a streaming Claude response into a single message. Marquee demo.
  • multi-message-agent — agent emitting multiple <Message>s with a <Photo> attachment, demonstrating cross-message reconciliation.
  • forum-thread-router — routes replies into specific topic threads of a Telegram forum supergroup via <Message threadId>.

Status

0.x — API stable, expect minor breakage as the underlying renderer evolves.

License

ISC