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

@sevenfold/setto-client

v0.5.0

Published

React client library for Setto — git-based inline CMS for Vite + i18next apps.

Readme

@setto/client

React client library for Setto — git-based inline CMS for Vite + i18next apps.

Published on npm as @sevenfold/setto-client (Sevenfold org). Import as @setto/client in app code.

Editors authenticate via Supabase, edit text inline on the live page, pick section colours from the site brand palette, and publish changes back to GitHub. setto-server commits whitelisted files and tracks the Vercel deployment.

Install

Sevenfold sites pin a git tag, not an npm version, so changes ship without an npm round-trip:

"@setto/client": "github:nitech/setto-client#v0.5.0"

Bun clones the repo on bun install, runs the package's prepare script to build dist/, and links it as @setto/client. Vercel does the same automatically when it installs build deps.

The package is also still published to npm (@sevenfold/setto-client) for external consumers:

bun add @setto/client@npm:@sevenfold/setto-client

Peer deps: react, react-dom, react-i18next, i18next.

For local monorepo development the consumer's vite.config.ts aliases @setto/client to setto-client source — see Local development.

Releasing a new version

  1. Bump version in package.json following semver (patch / minor / major).
  2. Commit and tag: git tag v0.5.1 && git push origin main --follow-tags.
  3. In each consumer (carryon.no, setto-site):
    • Update the tag in package.json (e.g. #v0.5.0#v0.5.1).
    • Run bun install to refresh the lockfile.
    • Push — Vercel pulls the new tag on the next deploy.

GitHub Actions still publishes to npm automatically when package.json lands on main (requires NPM_TOKEN secret), but Sevenfold consumers no longer wait for it.


Quick start

1. Wrap the app

// main.tsx
import { SettoProvider } from '@setto/client';
import sectionsTheme from './theme/sections.json';
import { brandColors } from './theme/brand-colors';
import { sectionSchemas } from './theme/section-schemas';

createRoot(document.getElementById('root')!).render(
  <SettoProvider
    config={{
      siteId: 'my-site',                        // slug in setto-server `sites` table
      apiUrl: import.meta.env.VITE_SETTO_API_URL,
      supabase: {
        url: import.meta.env.VITE_SUPABASE_URL,
        anonKey: import.meta.env.VITE_SUPABASE_ANON_KEY,
      },
      theme: sectionsTheme,                     // bundled defaults for section colours
      themePath: 'src/theme/sections.json',     // GitHub path — must be in content_paths
      brandColors,                              // palette for the colour toolbar
      sectionSchemas,                           // which fields each section exposes
    }}
  >
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </SettoProvider>,
);

2. Mount the Setto route

Editors sign in once at sitenavn.no/setto. After that, every visit to the public site auto-enters edit mode for as long as the Supabase session persists.

// App.tsx
import { SettoAdminApp } from '@setto/client';

<Routes>
  <Route path="/" element={<Home />} />
  <Route path="/setto/*" element={<SettoAdminApp />} />
</Routes>

3. Replace copy with <T>

import { T } from '@setto/client';

<h1><T k="hero.headline" /></h1>
<p><T k="hero.subheadline" /></p>

{/* Dynamic keys work too */}
<T k={`faq.items.${item.key}.q`} />

Keep using t() for non-visible strings (placeholders, aria-label, alt) — those are not inline-editable in v0.

4. Wire section colours (optional)

import { SettoSection, useSectionTheme } from '@setto/client';

function ValuesSection() {
  const colors = useSectionTheme('values');

  return (
    <SettoSection sectionId="values" id="verdier" className="py-20">
      <span style={{ color: colors.label }}><T k="values.label" /></span>
      <h2 style={{ color: colors.heading }}><T k="values.headline" /></h2>
    </SettoSection>
  );
}

Environment variables (host app)

| Variable | Purpose | |----------|---------| | VITE_SUPABASE_URL | Supabase project URL | | VITE_SUPABASE_ANON_KEY | Supabase anon key (public) | | VITE_SETTO_API_URL | setto-server base URL, e.g. http://localhost:3001 |


Edit mode

Edit mode activates whenever all of these are true:

  1. The user has an authenticated Supabase session.
  2. The user has access to config.siteId (a row in the sites table that they can read).
  3. The current path is not /setto (the dashboard is rendered without inline editing).

Sign-in at /setto persists via Supabase, so subsequent visits drop straight into edit mode — no URL flag, no extra step.

What editors see

A small round Setto button floats in the bottom-right corner. While there are no unpublished changes it stays as a circle showing only the Setto mark. As soon as you edit something it grows into a pill containing Publiser and a menu with:

| Item | Effect | |------|--------| | Avbryt | Discards every unsaved draft (text, section colours, image uploads) | | Logg ut | Signs out via Supabase and exits edit mode | | Historikk | Opens the /setto dashboard with deployment history |

| Action | How | |--------|-----| | Edit text | Click any <T> element — it becomes contentEditable | | Edit section colours | Click a section or block background (not text) | | Follow a link | Ctrl/Cmd + click (desktop) · Naviger ↗ chip when focused (touch) | | Publish | FAB → Publiser (visible once you have drafts) |

When you click a section or block, a compact colour toolbar appears above it. Click again or press Escape to dismiss.


Text editing (<T>)

<T k="dotted.key" /> renders the i18next string for the active language.

In edit mode the text is inline-editable. Changes are stored in an in-memory draft layer (I18nStore) and applied to the live i18next bundle immediately so the page re-renders.

On publish, the full locale bundles are serialised to JSON and committed to GitHub:

src/i18n/locales/no.json
src/i18n/locales/en.json

When edit mode starts, setto-server loads the current files from GitHub as the baseline (GET /sites/:id/content).


Section colours

Section colours live in a separate JSON file (not in i18n):

src/theme/sections.json

Example:

{
  "values": {
    "background": "#640AFF",
    "label": "#C9C0DA",
    "heading": "#FFFFFF",
    "icon": "#FFFFFF",
    "cardTitle": "#FFFFFF",
    "cardDesc": "#C9C0DA"
  }
}

Brand palette (brandColors)

Editors do not get a free-form colour picker. Each colour field shows the current swatch; clicking it opens a menu of predefined brand colours:

// theme/brand-colors.ts
import type { BrandColor } from '@setto/client';

export const brandColors: BrandColor[] = [
  { label: 'Beige', value: '#E6DCCF' },
  { label: 'Oliven', value: '#362F00' },
  { label: 'Lilla', value: '#640AFF' },
  // …
];

Rules for integrators:

  • List every colour token editors should be able to pick.
  • Values must match the palette used in Tailwind/CSS on the site (same hex values).
  • Use solid hex colours in sections.json — rgba values won't match swatches.
  • When you add a new brand token to Tailwind, add it to brandColors too.

Section schemas (sectionSchemas)

Define which colour fields each section exposes in the toolbar:

// theme/section-schemas.ts
import type { SectionSchema } from '@setto/client';

export const sectionSchemas: Record<string, SectionSchema> = {
  values: {
    label: 'Verdier',
    fields: [
      { key: 'background', label: 'Bakgrunn' },
      { key: 'label', label: 'Etikett' },
      { key: 'heading', label: 'Overskrift' },
      // keys must match properties used in useSectionTheme('values')
    ],
  },
};

The sectionId prop on <SettoSection> must match a key in both sectionSchemas and sections.json.

Applying colours in components

Read tokens with useSectionTheme(sectionId) and apply via inline style (or CSS variables you control):

const colors = useSectionTheme('hero');

<SettoSection sectionId="hero" style={{ /* background applied automatically */ }}>
  <h1 style={{ color: colors.heading }}><T k="hero.headline" /></h1>
</SettoSection>

<SettoSection> always applies colors.background as backgroundColor. Other tokens are your responsibility.

Nested blocks (<SettoBlock>)

For cards or panels inside a section, wrap each in <SettoBlock> with its own theme key:

import { SettoSection, SettoBlock, useSectionTheme } from '@setto/client';

function InnovationSection() {
  const card = useSectionTheme('innovationCard');

  return (
    <SettoSection sectionId="innovation" className="py-20">
      <SettoBlock blockId="innovationCard" className="p-8">
        <span style={{ color: card.label }}><T k="innovation.label" /></span>
        <h3 style={{ color: card.heading }}><T k="innovation.headline" /></h3>
      </SettoBlock>
    </SettoSection>
  );
}

Click a block's background to edit that block's colours. Click the section padding to edit the section background. Add matching keys to sections.json and sectionSchemas for each block.


Server setup (setto-server)

Site configuration is split in two:

1. Supabase sites row — control plane (where + routing). Set once when the site is registered (platform admin → Ny side):

| Column | Example | |--------|---------| | id | carryon-no (must match siteId in SettoProvider) | | repo_owner / repo_name / branch | GitHub target | | vercel_project_id | prj_… (used to route Vercel webhooks) |

2. setto.config.json in the site repo root — content shape. setto-server reads this from GitHub (cached, with a DB fallback):

{
  "displayName": "Carry On",
  "contentPaths": [
    "src/i18n/locales/no.json",
    "src/i18n/locales/en.json",
    "src/theme/sections.json",
    "public/images/setto/"
  ],
  "allowedOrigins": ["https://carryon.no", "http://localhost:3000"]
}
  • contentPaths is the publish whitelist — only these paths can be committed. Add new content files here before publishing them. setto.config.json itself is intentionally not in the list, so editors can never widen their own access.
  • allowedOrigins is the CORS allow-list; the first entry is also used as the editor-invite activation domain.

Keeping this in the repo means content shape lives with the code that defines it, and no DB change is needed when you add a content file — just commit the config. The legacy content_paths / allowed_origins / display_name DB columns remain as a fallback for sites without the file.


Publish flow

  1. Editor clicks Publiser in the floating FAB.
  2. Client serialises changed locale bundles + sections.json (if theme drafts exist).
  3. POST /sites/:siteId/publish with { files: [{ path, content }] }.
  4. setto-server validates paths against content_paths, commits to GitHub.
  5. A deployments row is inserted; the toolbar shows build status via Supabase Realtime + Vercel webhook.

Drafts are cleared after a successful publish.


Admin app (SettoAdminApp)

Route: /setto/*

Provides Supabase email/password login (invite-only — no self-service sign-up), password reset, and a dashboard. After sign-in the dashboard redirects to the site home, where edit mode auto-activates. Invite links from Supabase land on /setto to set a password. Does not render the inline editor itself.


Local development (Sevenfold monorepo)

When consumed by carryon.no, Vite aliases @setto/client to src/index.ts so changes hot-reload without a separate watch build.

Typical stack:

# Terminal 1 — Supabase
cd setto-server && supabase start

# Terminal 2 — API
cd setto-server && bun run dev   # :3001

# Terminal 3 — Site
cd carryon.no && bun run dev     # :3000

Build (library)

bun install
bun run build

Produces dist/setto-client.js and .d.ts via Vite library mode. Only needed before publishing to npm.


API surface

| Export | Purpose | |--------|---------| | SettoProvider / useSetto | Context, auth, stores, edit mode flag | | T | Inline-editable translation | | SettoSection | Section wrapper + edit selection | | SettoBlock | Nested card/panel with its own colour toolbar | | useSectionTheme | Read section colour tokens | | SettoAdminApp | /setto login + dashboard | | AuthGate | Standalone login wrapper | | BrandColor, SectionSchema, SettoConfig | Types for host config |


Limitations (v0)

  • <T> supports text content only — not HTML attributes (placeholder, alt, aria-label).
  • No list/repeater UI (cannot add a new FAQ row from the editor).
  • Drafts are in-memory — refresh discards unpublished changes.
  • Section colour toolbar only offers brandColors — no custom hex/rgba input.
  • CTA card colours and nested component colours are not section-themeable yet.
  • Single editor per site (no concurrent-edit conflict handling).