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

@ohhwells/bridge

v0.1.84

Published

The OhhWells canvas editor bridge — a standalone npm package that enables inline text and image editing for any site deployed on the OhhWells platform.

Readme

@ohhwells/bridge

The OhhWells canvas editor bridge — a standalone npm package that enables inline text and image editing for any site deployed on the OhhWells platform.

What it does

When a studio owner opens their site in the OhhWells canvas editor, the bridge:

  • Connects the iframe (the live site) to the parent canvas editor via postMessage
  • Enables click-to-edit for text, images, and background images
  • Handles draft saving and content hydration
  • Shows the "Add Section" insert line between sections in the canvas editor
  • Provides state toggle UI for editing hidden content (hover states, form views)
  • Injects scoped styles that never leak into the host template

Installation

npm install @ohhwells/bridge

Template setup (required steps)

1. Import styles

In your root layout file, import the bridge stylesheet once:

import "@ohhwells/bridge/styles";

2. Add the loader surface

The loader is a full-screen spinner shown while the bridge fetches personalised content. It hides itself once content is ready. Add this before your main content in the <body>:

import { OHW_LOADER_STYLE, OhwLoaderSpinner } from "@ohhwells/bridge";

// Inside <body>:
<div
  id="ohw-loader"
  suppressHydrationWarning
  style={{ ...OHW_LOADER_STYLE, display: "none" }}
>
  <OhwLoaderSpinner />
</div>;

{
  /* Inline script — shows the loader immediately on the client before React hydrates */
}
<script
  dangerouslySetInnerHTML={{
    __html: `(function(){try{
      var p=location.hostname.split(".");
      var fromHost=p.length>=3&&p[0]!=="www"?p[0]:"";
      var fromQuery=new URLSearchParams(location.search).get("subdomain")||"";
      if(!fromHost&&!fromQuery)return;
      var e=document.getElementById("ohw-loader");
      if(e)e.style.display="flex"
    }catch(e){}})();`,
  }}
/>;

The inline script detects whether the page is being loaded under a subdomain (personalised content mode) and shows the loader before React has a chance to hydrate, preventing a flash of the default content.

3. Mount OhhwellsBridge

Add <OhhwellsBridge /> inside a <Suspense> boundary in your root layout. It must be in <Suspense> because it calls useSearchParams() internally.

import { Suspense } from "react";
import { OhhwellsBridge } from "@ohhwells/bridge";

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        {/* loader + inline script here (see step 2) */}

        <Suspense>
          <OhhwellsBridge />
        </Suspense>

        {/* rest of your layout */}
        {children}
      </body>
    </html>
  );
}

The bridge activates automatically when the page is loaded inside the OhhWells canvas editor. It does nothing in production (live site) mode.

4. Mark sections

Every top-level section on each page must have a unique data-ohw-section attribute. This is what the canvas editor uses to:

  • Show the "Add Section" insert line between sections
  • Persist widget insertions (saving which section a widget was inserted after)
// Good — section element is the direct content root
<section data-ohw-section="hero">
  ...
</section>

// Also fine — any element type works
<div data-ohw-section="testimonials">
  ...
</div>

Naming rules:

  • Use kebab-case
  • Must be unique across the entire page
  • Must be stable — if a section is renamed, any saved widget insertions referencing that name will break

Example — a page with multiple sections:

export default function HomePage() {
  return (
    <>
      <section data-ohw-section="hero">...</section>
      <section data-ohw-section="lagree-intro">...</section>
      <section data-ohw-section="classes-strip">...</section>
      <section data-ohw-section="testimonials">...</section>
      <section data-ohw-section="plan-form">...</section>
    </>
  );
}

5. Mark editable elements

Add data-ohw-editable and data-ohw-key to any element the studio owner should be able to edit:

{
  /* Editable rich text (bold, italic, etc.) */
}
<h1 data-ohw-editable="text" data-ohw-key="hero-heading">
  Welcome to the studio
</h1>;

{
  /* Editable plain text (no formatting) */
}
<p data-ohw-editable="plain" data-ohw-key="hero-subtitle">
  Book your first class
</p>;

{
  /* Editable image */
}
<img
  data-ohw-editable="image"
  data-ohw-key="hero-image"
  src="/hero.jpg"
  alt="Hero"
/>;

{
  /* Editable background image */
}
<div
  data-ohw-editable="bg-image"
  data-ohw-key="hero-bg"
  style={{ backgroundImage: "url(/bg.jpg)" }}
/>;

Key naming rules:

  • Must be globally unique across all pages
  • Use kebab-case
  • Must be stable — changing a key orphans any saved content for that element

6. Bridge-managed links (nav, footer, CTAs)

For links whose destination URL is edited in the canvas, use a plain <a> with data-ohw-href-key. Put the editable label on an inner span:

<a href="/book" data-ohw-href-key="nav-book-href" data-ohw-role="navbar-button">
  <span data-ohw-editable="text" data-ohw-key="nav-book-label">
    Book a Class
  </span>
</a>

Section / body CTAs use the same two-phase select + Edit-link toolbar with data-ohw-role="button" (preferred). navbar-button remains supported as an alias for nav header CTAs:

<a href="/contact" data-ohw-href-key="hero-cta-href" data-ohw-role="button" data-ohw-drag-disabled="true">
  <span data-ohw-editable="text" data-ohw-key="hero-cta-label">
    Get started
  </span>
</a>

| Attribute | Purpose | |-----------|---------| | data-ohw-href-key | Storage key for the link destination | | href | Default URL for SSR / first paint | | data-ohw-key + data-ohw-editable on inner span | Editable label text | | data-ohw-role="button" | Section CTA — two-phase select + link-action toolbar (no reorder/More) | | data-ohw-role="navbar-button" | Alias of button for navbar / header CTAs |

The bridge persists and restores href values (including after React re-renders). No template-side wrapper component is required.


SchedulingWidget (vibe-coder placement)

SchedulingWidget lets a template builder embed a scheduling/booking section directly in their template JSX, without going through the "Add Section" flow. The canvas editor treats it exactly like a dynamically-inserted scheduling widget — the studio owner can connect a schedule, switch it, and clear it.

Basic usage

import { SchedulingWidget } from '@ohhwells/bridge'

export default function ClassesPage() {
  return (
    <>
      <PageHeader ... />
      <ClassLibrary ... />
      <SchedulingWidget />
      <WordmarkBand ... />
    </>
  )
}

No props are required. The widget auto-generates a stable identity via React's useId() so the bridge can track which schedule is connected to it across saves.

Props

| Prop | Type | Default | Description | |---|---|---|---| | insertAfter | string | auto | Stable identifier used as the tracker key. Only set this manually if you need two SchedulingWidgets on the same page. | | initialScheduleId | string \| null | undefined | Set by the bridge internally after hydration. Do not pass this yourself. | | notifyOnConnect | boolean | false | Set by the bridge internally. Do not pass this yourself. |

How it works

  1. The widget renders immediately with a loading skeleton.
  2. It sends ow:request-schedule-config to the bridge (running in the same window).
  3. The bridge looks up the tracker for a saved scheduleId for this widget:
    • Found → responds with ow:schedule-config { scheduleId } → widget loads that schedule.
    • Not found (first time) → bridge adopts the widget (adds it to the tracker, notifies the canvas editor), responds with scheduleId: null → widget shows empty state with an "Add Schedule" button.
  4. In the canvas editor, the studio owner clicks "Add Schedule" → selects a schedule → the widget updates and the connection is saved.
  5. On the live site, the widget fetches the saved schedule by ID and renders it.

Multiple widgets on one page

Each SchedulingWidget must have a unique insertAfter to be tracked independently:

<SchedulingWidget insertAfter="classes-morning" />
<SchedulingWidget insertAfter="classes-evening" />

If you omit insertAfter on both, useId() auto-generates different stable IDs for each, so they are tracked separately anyway.

Empty state on live site

If the studio owner has never connected a schedule to this widget, it renders nothing on the live site (no empty placeholder is shown to visitors).


Editable states (advanced)

For elements with multiple display states (e.g. a contact form with default/success/error views), wrap each state in a data-ohw-state-view and mark the container with data-ohw-editable-state:

<div
  data-ohw-editable-state="default,success,error"
  data-ohw-key="contact-form"
>
  <div data-ohw-state-view="default">{/* default form UI */}</div>
  <div data-ohw-state-view="success">{/* success message */}</div>
  <div data-ohw-state-view="error">{/* error message */}</div>
</div>

The bridge shows a state toggle in the canvas editor to switch between states.


Local development workflow

When iterating on the bridge package itself:

# 1. Build the package
cd ohhwells-bridge
npm run build

# 2. Link it globally (one-time setup)
npm link

# 3. In your template repo, use the local build instead of the npm version
cd rebound-template
npm link @ohhwells/bridge

After that, every npm run build in ohhwells-bridge is picked up by the template immediately (no re-link needed). To go back to the npm version:

cd rebound-template
npm unlink @ohhwells/bridge
npm install

Publishing

The package publishes automatically via GitHub Actions on push to main (production tag) or staging (next tag). To publish manually:

npm run build
npm publish --access public

Staging → rebound-template auto-bump

When staging publishes successfully, the bridge workflow dispatches a bridge-published event to TheFlowOps-Eng/rebound-template. That repo’s bump-bridge.yml workflow checks out staging, runs npm install @ohhwells/bridge@<version> --save-exact, and pushes the lockfile bump.

One-time setup (in ohhwells-bridge GitHub repo → Settings → Secrets):

| Secret | Value | |--------|--------| | REBOUND_TEMPLATE_DISPATCH_TOKEN | Fine-grained or classic PAT with repo access to rebound-template (needs permission to trigger repository_dispatch) |

The published staging version looks like 0.1.31-next.42 and is tagged next on npm.


Link dialog (Canvas Editor)

LinkPopover is rendered inside OhhwellsBridge (iframe portal) when editing link destinations. It uses the shadcn Dialog pattern (Radix) — centered modal with overlay — implementing the Figma shadcn kit panel (nodes 8365-7616 / 8382-4161).

// Used internally by OhhwellsBridge — external consumers rarely mount this directly.
import { LinkPopover } from "@ohhwells/bridge";

The iframe bridge reports page sections on ow:ready:

{ "type": "ow:ready", "version": "1", "bridgeVersion": "0.1.50", "nodes": [...], "sections": [{ "id": "hero", "label": "Hero" }] }

version is the protocol version; bridgeVersion is the npm package version (inlined at build time), letting the editor detect sites running an older bridge and gate version-dependent features (e.g. AI prompting) gracefully.

Templates must mark sections with data-ohw-section (and optional data-ohw-section-label).

Section-scoped content collection (AI prompt context)

The editor can request a single section's editable content (edit mode only):

// editor → iframe
{ "type": "ow:collect-section", "sectionId": "hero" }

// iframe → editor
{ "type": "ow:section-nodes", "sectionId": "hero", "found": true,
  "nodes": [{ "key": "hero-headline", "type": "text/rich", "text": "..." }],
  "rect": { "top": 0, "left": 0, "width": 1280, "height": 720 } }
  • found is false (with empty nodes, rect: null) when no [data-ohw-section="<id>"] exists on the current page.
  • rect is the section element's document-relative bounding box — the editor uses it to draw the in-flight overlay over just the target section.
  • The returned set contains pure section content only: structural keys (__ohw_*), carousel/video-setting/meta nodes, and keyless editables are never included.
  • Node type is normalized to the set image | bg-image | video | link | plain | text/rich | text (unknown template-declared types become text).

ow:enter-edit (iframe → editor, sent when the user clicks into an editable) also carries the enclosing section so the editor can offer that section as the AI prompt target:

{ "type": "ow:enter-edit", "key": "hero-headline", "sectionId": "hero", "sectionLabel": "Hero" }

sectionId/sectionLabel are null for editables outside any [data-ohw-section] (label also when data-ohw-section-label is absent).

Local link-editor testing (dev fixtures)

Without the canvas editor parent, add ohw-fixtures=1 to the edit URL. This preloads Re:Bound pages and sections into the link popover:

http://localhost:3000 /?mode=edit&ohw-fixtures=1
  1. Click a nav link label (e.g. Pricing) → toolbar → chain icon
  2. Pick About in Destination → Choose a sectionPersonal training
  3. Save — href becomes /about#personal-training
  4. Open http://localhost:3001/about#personal-training (without mode=edit) to verify scroll

In edit mode link clicks are blocked; check href in DevTools or test on a normal page load.


Design tokens

The package ships the full OhhWells design token set, scoped to [data-ohw-bridge-root] so styles never leak into the host template.

Semantic colors (CSS variables on [data-ohw-bridge-root])

| Token | Light | Dark | | -------------------- | --------- | --------- | | primary | #0f172a | #f8fafc | | primary-foreground | #f8fafc | #0f172a | | background | #ffffff | #020617 | | foreground | #020617 | #f8fafc | | muted | #f1f5f9 | #1e293b | | muted-foreground | #64748b | #94a3b8 | | border | #e2e8f0 | #334155 | | destructive | #dc2626 | #7f1d1d | | success | #16a34a | #22c55e |

Brand palette

Re:Bound brand colors: bone, ivory, sand, linen, stone, clay, umber, umber-deep, espresso, clove, ink, ash, carbon.


License

MIT