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

@rubyeverywhere/bridge

v0.4.0

Published

One API for browser, desktop, and mobile — the JS half of RubyEverywhere

Readme

@rubyeverywhere/bridge

One API for the browser, the RubyEverywhere desktop shell, and (soon) Hotwire Native mobile apps — the JavaScript half of RubyEverywhere.

App code writes to this surface only; platform differences live in adapters inside the package. Everything degrades gracefully: the same page works in a plain browser tab and gains native powers inside a RubyEverywhere app.

Install

npm install @rubyeverywhere/bridge

Using RubyEverywhere? You don't need npm at all: the ruby_everywhere gem ships this exact package. Rails apps get it served and importmap-pinned by the gem's engine (updates arrive with bundle update ruby_everywhere); Sinatra and Hanami apps get it vendored to public/bridge.js by every install.

Usage

import Everywhere from "@rubyeverywhere/bridge"

Everywhere.platform                  // "desktop" | "mobile" | "browser"
Everywhere.os                        // "macos" | "windows" | "linux" | "ios" | "android" | "chromeos" | "unknown"
Everywhere.native                    // true inside a RubyEverywhere app
Everywhere.version                   // app version; null in a plain browser tab

Everywhere.notify({ title, body })   // native notification / web Notification / console
Everywhere.confirm("Sure?")          // Promise<boolean>, native dialog when possible
Everywhere.on("menu", handler)       // shell events; returns an unsubscribe fn
Everywhere.visit("/settings")        // Turbo.visit with location fallback

Everywhere.menu({ title, items })    // Promise<item | null> — native action
                                     // sheet in the shell, a styled bottom
                                     // sheet everywhere else. items:
                                     // [{ title, id?, style?, disabled? }]

Everywhere.clipboard.write(text)     // Promise<void>
Everywhere.clipboard.read()          // Promise<string | null>

Everywhere.haptics.impact("medium")  // real haptics in the mobile shell;
Everywhere.haptics.notification("success") // Vibration API in browsers, no-op elsewhere
Everywhere.haptics.selection()

Everywhere.badge.set(3)              // app icon badge (mobile / installed PWAs)
Everywhere.badge.clear()
Everywhere.badge.setTab("/inbox", 3) // native tab bar badge (mobile)
Everywhere.badge.clearTab("/inbox")

Everywhere.permissions.query("camera")   // Promise<{name, status}>
Everywhere.permissions.request("camera") // prompts when possible
Everywhere.permissions.openSettings()    // for the denied dead-end (iOS asks once)
// status: "granted" | "denied" | "prompt" | "undeclared" | "unsupported"
// mobile permissions must be declared in everywhere.yml (camera/location
// declarations carry the usage string iOS shows) — undeclared never prompts

Everywhere.storage.get("key")        // Promise<any | null> — device-local settings
Everywhere.storage.set("key", value) // Promise<boolean>; any JSON value round-trips
Everywhere.storage.remove("key")     // Promise<boolean>
Everywhere.storage.clear()           // Promise<boolean> — clears app keys only
// UserDefaults in the mobile shell (survives web view resets and cache
// clears, unlike localStorage); localStorage-backed in browsers and the
// desktop shell. Settings, not secrets — tokens go in biometrics.credential.

Native chrome (nav bar buttons, menus, action sheets)

Tag ordinary markup, and inside the mobile shell the bridge lifts it into real native controls — a nav-bar button, a pull-down / overflow menu, or an action sheet. Tapping the native control just .click()s the element it mirrors, so a link navigates, a submit submits, and behavior stays defined once in the DOM. In a browser the same markup is the plain HTML it already is. No JavaScript, no Stimulus — CSP-safe.

<!-- becomes a nav-bar button (right side); icons are per-platform like tabs -->
<a href="/notes/new" data-everywhere-nav-button
   data-everywhere-nav-title="New"
   data-everywhere-nav-icon-ios="plus"
   data-everywhere-nav-icon-android="add">New</a>

<!-- a submit button in the nav bar -->
<button type="submit" data-everywhere-nav-button
        data-everywhere-nav-title="Save" data-everywhere-nav-style="done">Save</button>

<!-- a nav-bar pull-down / overflow menu (defaults to the ⋯ icon) -->
<div data-everywhere-nav-menu data-everywhere-nav-title="More">
  <a href="/share" data-everywhere-menu-item data-everywhere-menu-title="Share">Share</a>
  <button form="delete_1" data-everywhere-menu-item
          data-everywhere-menu-title="Delete"
          data-everywhere-menu-style="destructive">Delete</button>
</div>

<!-- an in-content action sheet: a trigger and the items it opens -->
<div data-everywhere-menu>
  <button data-everywhere-menu-trigger>Options</button>
  <div data-everywhere-menu-items>
    <a href="/x/edit" data-everywhere-menu-item>Edit</a>
  </div>
</div>

Rails ships helpers that emit exactly this — everywhere_nav_button, everywhere_submit_button, everywhere_nav_menu / everywhere_menu_item, everywhere_menu, and everywhere_fab (a floating action button). Include everywhere/native.css for the FAB, the action-sheet fallback, and the safe-area utilities.

Multi-instance apps

Apps whose users pick a server — one hosted platform, many instances — opt in with remote.instances: true in everywhere.yml. The mobile shell then boots into remote.url (your hosted picker page) until that page picks an instance:

Everywhere.instance.supported        // true in a shell built with remote.instances
Everywhere.instance.current          // active instance URL, or null (= built-in root)

Everywhere.instance.set("https://acme.example.com")
// validates, persists, and fully resets the app onto the instance — every
// later launch boots straight there. {to: "/path"} lands somewhere specific.

Everywhere.instance.clear()          // back to the picker (and resets again)

In a plain browser set() simply navigates to the URL — going there is what picking an instance means on the web.

Declarative forms need no JS: elements with data-everywhere-haptic ("light", "impact:heavy", "notification:error", "selection") play on tap, and <meta name="everywhere:badge"> / <meta name="everywhere:tab-badge"> tags (the everywhere_badge / everywhere_tab_badge Rails helpers) are applied on every Turbo visit.

Auto-updates

Inside the desktop shell, apps with an update feed configured can check, install, and switch channels at runtime:

Everywhere.updates.supported          // true when the shell has an update feed
Everywhere.updates.channel            // effective channel ("stable", "beta", …)

const result = await Everywhere.updates.check()
// { available: true, version, notes, notesHtml } or { available: false }

Everywhere.updates.install()          // download / verify / swap / relaunch
Everywhere.updates.setChannel("beta") // Promise<{ channel }>, persisted by the shell

Everywhere.updates.on("available", handler)
// events: "available" | "none" | "progress" | "ready" | "error" | "channel"

notes is the release-notes markdown source; notesHtml is the same notes pre-rendered to HTML from your signed update feed, ready for a changelog modal: el.innerHTML = notesHtml.

In a plain browser tab the updates API is inert: check() resolves { available: false, unsupported: true } and nothing throws.

Platforms

  • desktop — the Tauri-based RubyEverywhere shell. Notifications, dialogs, clipboard, shell events, and auto-updates are native.
  • browser — honest web fallbacks: the Notification API, window.confirm, navigator.clipboard, DOM CustomEvents.
  • mobile — Hotwire Native (iOS shell). notify() delivers a native local notification via the shell's everywhere--notification bridge component (web-bridge handshake is installed automatically unless the app already loads @hotwired/hotwire-native-bridge — first one wins, like the official package). confirm()/clipboard use the browser fallbacks, which are native in a WKWebView anyway. Everything degrades to browser behavior outside the shell.

License

MIT