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.45.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 an npm version via alias:

"@setto/client": "npm:@sevenfold/[email protected]"

bun install pulls the prebuilt package from npm — no GitHub token required on Vercel.

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 push to main. GitHub Actions publishes @sevenfold/setto-client to npm automatically via npm trusted publishing (OIDC — no registry token in this repo).
  3. Confirm the version is on npm: npm view @sevenfold/[email protected] version.
  4. In each consumer (setto-site, the setto-agent templates, every generated site):
    • Update the pin in package.json (e.g. @0.7.0 → @0.7.1).
    • Run bun install to refresh the lockfile.
    • Commit and push — Vercel installs the new npm version on the next deploy.

Why npm, not a git tag? nitech/setto-client is private. Vercel cannot fetch github:nitech/setto-client#v… without a GITHUB_TOKEN with repo access. Public npm avoids that and ships a prebuilt dist/.


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-left 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) | | Historikk | Opens the /setto dashboard with deployment history | | Innstillinger | Opens the /setto dashboard on the module settings — the forms inbox and who receives form email | | Logg ut | Signs out via Supabase and exits edit mode |

| 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.


Forms (SettoForm)

<SettoForm formId="kontakt" /> renders a form whose fields live in the locale bundle, so editors can add, remove, reorder and relabel them in edit mode like any other copy. Submissions go to setto-server, which stores them and emails the site's recipients.

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

<SettoForm formId="kontakt" className="grid gap-4" />

The locale supplies the texts and the numbered field list:

"form": {
  "kontakt": {
    "submit": "Send melding",
    "success": "Takk! Vi tar kontakt så snart vi kan.",
    "error": "Noe gikk galt. Prøv igjen.",
    "fields": {
      "1": { "name": "navn", "type": "text", "label": "Navn", "required": "true" },
      "2": { "name": "epost", "type": "email", "label": "E-post", "required": "true" },
      "3": { "name": "melding", "type": "textarea", "label": "Melding" }
    }
  }
}

Field types: text, email, tel, number, url, date, textarea, select, radio, checkbox, checkboxes (FORM_FIELD_TYPES). select, radio and checkboxes take newline-separated options. All values are strings — "required": "true", not true.

  • Styling. Every part carries a setto-form__* class (__field, __label, __input, __textarea, __select, __button, __error, __success, …) plus data-setto-field="<name>" on each field wrapper. A minimal zero-specificity baseline is injected once, so any site rule wins; classNames puts classes on the parts directly. A site styling its own button should set border: 0 (the baseline draws a 1px currentColor border).
  • Success. The form is replaced by a role="status" panel with the locale's success text and a "send another" link; focus moves to it.
  • Spam. A honeypot, a time-to-submit measurement and controlled inputs on the client; Origin check, per-site and per-address rate limits and a link-count heuristic on the server. Nothing asks the visitor to prove anything, and a caught bot sees a normal success.
  • Own backend. Pass endpoint="https://…" to POST the bare values there instead of to Setto.
  • Inbox and recipients. FAB → Innstillinger (or /setto?view=forms): submission history and the recipient list. With no recipients configured, the site's owners receive the email.

SettoForm is the first Setto module — a feature with a client component, an admin view and a server route. SETTO_MODULES lists them; the admin app renders one tab per module.


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 | | SettoImage, SettoIcon, SettoVideo | Editable image (upload), icon and video leaves | | SettoRepeater | Editable list stored in i18n (add/remove items) | | SettoAnimation | Entrance animation wrapper (fadeIn, slideUp, stagger, scrollReveal) | | SettoBlockTree | Renders a home.layout.json block tree with the components above | | GuestEditProvider / useGuestEdit | Session-only "try editing" mode for a marketing site — no auth, never published | | SettoForm, FORM_FIELD_TYPES | Editable form that submits to Setto (forms module) | | SETTO_MODULES | Registry of modules the admin app shows settings for | | SettoAdminApp | /setto login + dashboard (history, module settings) | | SETTO_BASE, adminViewUrl | The /setto path and links into a dashboard view | | 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); form field placeholders are the exception, edited through the field editor.
  • Lists are SettoRepeater (add/remove items); repeater items cannot be reordered yet — form fields can.
  • Drafts are in-memory — refresh discards unpublished changes.
  • Section colour toolbar only offers brandColors — no custom hex/rgba input.
  • Only SettoSection and SettoBlock backgrounds and the tokens their schemas list are themeable — no per-element colour picking.
  • Single editor per site (no concurrent-edit conflict handling).

Knowledge base

.wiki/ is this repo's LLM-maintained knowledge bundle (Karpathy LLM-wiki pattern, OKF core): one page per component, edit-mode feature, block-tree module, store and workflow, each with code citations, contracts, failure modes and doc-drift notes. Start at .wiki/index.md (catalog) or .wiki/overview.md (the one page to read first). Keep it in sync with code changes as described in .wiki/AGENTS.md.