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

@fromluke/mobile-previewer

v0.1.3

Published

Mobile device shell for previewing React prototypes to clients. Drop your prototype inside <Previewer>, share the Vercel link.

Readme

@fromluke/mobile-previewer

Mobile device shell for previewing React prototypes to clients. Drop your prototype inside <Previewer>, push to GitHub, share the Vercel link.

import { Previewer } from '@fromluke/mobile-previewer'
import '@fromluke/mobile-previewer/styles.css'
import Prototype from './prototype'

export default function App() {
  return (
    <Previewer client="Clean Simple Eats" title="PDP Optimizations • Rd 1">
      <Prototype />
    </Previewer>
  )
}
  • ≥ 789px: black canvas, top bar (client + title left, device dropdown right), centered phone shell. Smooth CSS-transition morph between iPhone 15 Pro / 15 Pro Max / SE. Selected device persists in localStorage.
  • < 789px (including mobile landscape): shell hides, your prototype renders full-bleed responsive.

API

<Previewer
  client="Client Name"             // small text, top-left
  title="Project Name"              // big text, top-left
  defaultDeviceId="iphone-15-pro"   // optional
  devices={DEVICES}                 // optional — override the device list
  shellBreakpoint={789}             // optional — viewport width below which the shell hides
  storageKey="mp:device"            // optional — localStorage key; pass null to disable persistence
>
  {/* your prototype */}
</Previewer>

Built-in devices: iphone-15-pro, iphone-15-pro-max, iphone-se. Add more by passing your own Device[]:

import { Previewer, DEVICES, type Device } from '@fromluke/mobile-previewer'

const PIXEL: Device = {
  id: 'pixel-8',
  label: 'Pixel 8',
  screen: { width: 412, height: 915, radius: 36 },
  shell: { paddingX: 12, paddingY: 12, radius: 48 },
}

<Previewer devices={[...DEVICES, PIXEL]} ... />

Authoring prototypes — constraints

The prototype renders inline inside a fixed-size container (no iframe). So:

  • Use 100%, not 100vw/100vh for sizing. Container is 393×852 for 15 Pro, etc.
  • Scroll the inside, not the page. Set overflow: auto on a wrapper inside your prototype.
  • Anything fixed (position: fixed) anchors to the viewport, not the phone screen. Use position: sticky or position: absolute parented to your prototype root.

Develop the shell

npm install
npm run dev         # demo playground at http://localhost:5173
npm run build       # library bundle → dist/
npm run typecheck

The demo lives in src/demo/. The library entry is src/index.ts.

See CLAUDE.md for package-internal architecture notes.


Publishing flow

Once set up, the only command you ever run to ship a change is npm version patch && git push --follow-tags. GitHub Actions does the rest.

How the pieces fit together

local edits
   │
   │  npm version patch          ← bumps package.json + creates git tag v0.1.1
   │  git push --follow-tags     ← pushes commits AND the tag
   ▼
GitHub
   │
   │  .github/workflows/release.yml  fires on `v*.*.*` tags
   │    1. checkout + npm ci
   │    2. typecheck + build
   │    3. verify tag matches package.json version
   │    4. npm publish --provenance
   │    5. create GitHub Release with auto-generated notes
   ▼
npm registry + GitHub Releases  (both updated from a single push)

Source of truth split:

  • GitHub = the source code. Commits, history, issues, releases.
  • npm = the published artifact (just dist/). Consumers install from here.

You don't push to both. You push to GitHub; npm publishes from CI.

One-time setup

Do these once, in order.

1. Create the npm account + Granular Access Token

  1. Sign up / log in at https://www.npmjs.com.
  2. (Publishing under @fromluke?) Make sure you own the fromluke username, or create an npm org with that name at https://www.npmjs.com/org/create. To use a different scope, search/replace @fromluke across this repo + any consumers.
  3. Go to https://www.npmjs.com/settings/[your-username]/tokens/new and fill out the Granular Access Token form:
    • Token name: github-actions-mobile-previewer
    • Bypass two-factor authentication (2FA): ✅ check it (CI can't do interactive 2FA)
    • Allowed IP ranges: leave blank (GitHub Actions IPs are dynamic)
    • Packages and scopes → Permissions: Read and write
      • Then below, pick "Only select packages and scopes" → add @fromluke/mobile-previewer (scope narrowly — a leaked token only affects this one package)
    • Organizations → Permissions: No access
    • Expiration: 365 days (set a calendar reminder to rotate)
    • Click Generate token, copy it — it's only shown once.

2. Create the GitHub repo

  1. https://github.com/new
  2. Owner fromluke, name mobile-previewer, visibility Public (npm provenance requires a public repo; private works if you remove "provenance": true from package.json).
  3. Don't initialize with README / license / gitignore — you have them locally.
  4. Create repository.

3. Push your local code

cd ~/repos/mobile-previewer
git add .
git status                     # sanity check
git commit -m "Initial mobile-previewer scaffold"
git remote add origin https://github.com/fromluke/mobile-previewer.git
git branch -M main
git push -u origin main

4. Add the token + workflow permissions on GitHub

In the new repo:

  • Settings → Secrets and variables → Actions → New repository secret
    • Name: NPM_TOKEN
    • Value: the token from step 1
  • Settings → Actions → General → Workflow permissions → "Read and write permissions" → Save (lets the release workflow create GitHub Releases)

5. Verify CI ran green

Actions tab on the repo → CI workflow on your initial push should be green. If red, fix before tagging.

6. (Optional) first manual publish — only if 0.1.0 isn't on npm yet

npm login
npm publish --access public

From here on, every publish goes through CI — never manual.

Day-to-day: shipping an update

# edit code...
git commit -am "Tighten dropdown caret"

# One command — bumps version, builds, commits, tags, pushes everything.
# The `postversion` script pushes the tag, which fires release.yml.
npm run release            # 0.1.0 → 0.1.1
# or: npm run release:minor / npm run release:major

Under the hood npm run release runs:

  1. npm version patch — bumps package.json, runs prepublishOnly (build), creates a commit + tag.
  2. postversiongit push --follow-tags, pushes the commit + tag.
  3. GitHub Actions sees the v*.*.* tag → publishes to npm with provenance.

Watch the workflow run at https://github.com/fromluke/mobile-previewer/actions. When it goes green, the new version is live on npm.

In each prototype:

npm install @fromluke/mobile-previewer@latest
git commit -am "Bump mobile-previewer"
git push                   # Vercel redeploys automatically

Or pin to a range like "^0.1.0" so npm update keeps prototypes current without per-version commits.

CI on every push

.github/workflows/ci.yml runs on every push and PR:

  • npm ci
  • npm run typecheck
  • npm run build
  • npm pack --dry-run (validates the publish manifest before tag time)
  • npm audit on production deps, fails on high/critical

Catches regressions before they can be tagged.


Spinning up a prototype

For each client + project (e.g. clean-simple-eats/homepage):

cd ~/repos/clean-simple-eats
npm create vite@latest homepage -- --template react-ts
cd homepage
npm install
npm install @fromluke/mobile-previewer

Replace src/main.tsx:

import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import { Previewer } from '@fromluke/mobile-previewer'
import '@fromluke/mobile-previewer/styles.css'
import { Homepage } from './prototype/Homepage'
import './index.css'

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <Previewer client="Clean Simple Eats" title="Homepage • Rd 1">
      <Homepage />
    </Previewer>
  </StrictMode>,
)

Delete the default Vite styles in src/index.css (the previewer brings its own — keep only html, body, #root { margin: 0; padding: 0; } and a body { background: #000 }). Then:

git init
git add .
git commit -m "Initial prototype"
# Create the repo at https://github.com/new (e.g. "cse-homepage")
git remote add origin https://github.com/fromluke/cse-homepage.git
git branch -M main
git push -u origin main

Then on Vercel:

  1. https://vercel.com/new → import the GitHub repo.
  2. Framework preset: Vite. Defaults are fine.
  3. Deploy → you get a URL like cse-homepage.vercel.app to share with the client.

Every git push to main redeploys.


Security posture

  • No runtime network access from the lib code. The only outbound request is the Google Fonts stylesheet for Inter + Roboto Mono (preconnect + <link> injection in src/loadFonts.ts). URLs are hardcoded constants.
  • No dangerouslySetInnerHTML, no eval, no Function() constructor. All rendered text passes through React's standard escaping.
  • localStorage access is guarded (typeof window check + try/catch). Safe in Safari private mode, disabled-storage environments, and SSR.
  • CSS is scoped under .mp-root / .mp-responsive — can't bleed into the consumer's prototype DOM.
  • files: ["dist"] restricts npm tarball to compiled output only. Source files, configs, .claude/, .github/ never ship to consumers.
  • npm provenance is enabled via publishConfig.provenance: true + the release workflow's OIDC token, so each published version is cryptographically linked to the exact public commit it was built from. Visible on the package page on npmjs.com.
  • Automation token in CI bypasses 2FA but is tied to a single npm account; rotate it from npmjs.com → tokens if compromised.
  • npm audit runs in CI on production deps, fails the build at --audit-level=high.