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

@lumianalytics/survey

v0.5.0

Published

Self-contained React widget for configurable Lumi surveys.

Readme

Lumi Survey

📖 Ny bruker? Se Kom i gang-guiden for å komme raskt i gang.

Selvstendig React-widget for å samle inn brukertilbakemeldinger via Lumi.

Ny her? Følg Kom i gang i rot-README for installasjon, backend-oppsett og NAIS-tilgang. Denne guiden dekker widget-konfigurasjon og avanserte features.

Sett opp en survey

Her er et komplett eksempel — en emoji-rating med valgfri oppfølgingstekst:

import "@lumianalytics/survey/styles.css";
import { LumiSurveyDock } from "@lumianalytics/survey";
import type { LumiSurveyTransport } from "@lumianalytics/survey";

const transport: LumiSurveyTransport = {
  submit: async (submission) => {
    await fetch("/api/survey", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(submission.transportPayload),
    });
  },
};

const survey = {
  type: "rating",
  questions: [
    {
      id: "rating",
      type: "rating",
      variant: "emoji",
      prompt: "Hvordan var opplevelsen din?",
      required: true,
    },
    {
      id: "feedback",
      type: "text",
      prompt: "Har du andre tilbakemeldinger?",
      maxLength: 1000,
      visibleIf: { field: "ANSWER", questionId: "rating", operator: "EXISTS" },
    },
  ],
};

<LumiSurveyDock surveyId="min-flate" survey={survey} transport={transport} />;

visibleIf gjør at tekstfeltet først vises etter at brukeren har valgt en emoji. Se Progresjon for flere eksempler.

Spørsmålstyper

En survey er et LumiSurveyConfig-objekt med spørsmål i rekkefølge.

Rating

Fire varianter med ulik skala:

| Variant | Skala | Beskrivelse | | :--- | :--- | :--- | | emoji | 1–5 | 😡🙁😐😀😍 | | thumbs | 1–2 | 👎👍 | | stars | 1–5 | ⭐⭐⭐⭐⭐ | | nps | 0–10 | Nummerte knapper med lav/høy-label |

{
  id: "nps",
  type: "rating",
  variant: "nps",
  prompt: "Hvor sannsynlig er det at du vil anbefale oss?",
  lowLabel: "Lite sannsynlig",
  highLabel: "Svært sannsynlig",
  required: true,
}

Text

Fritekstfelt med valgfri maks-lengde.

{
  id: "comment",
  type: "text",
  prompt: "Hva kan vi forbedre?",
  maxLength: 1000,
}

Single choice / Multi choice

{
  id: "reason",
  type: "singleChoice",
  prompt: "Hva var du her for å gjøre?",
  options: [
    { value: "apply", label: "Søke om noe" },
    { value: "status", label: "Sjekke status" },
    { value: "other", label: "Annet" },
  ],
}

multiChoice støtter variant: "checkbox" (default) eller variant: "combobox" for mange valg.

Progresjon: visibleIf

For de fleste surveyer er det nok å vise oppfølgingsspørsmål kun når det er relevant (progressive disclosure).

{
  id: "comment",
  type: "text",
  prompt: "Hva gikk galt?",
  visibleIf: {
    field: "ANSWER",
    questionId: "rating",
    operator: "LT",
    value: 3,
  },
}

Operatorer: EXISTS, EQ, NEQ, GT, LT, CONTAINS.

Storage-strategi

Widgeten husker at brukeren har lukket (dismissed) surveyen, og respekterer en valgfri cooldown-periode før den vises igjen. Du velger hvordan den husker dette:

| Strategi | Merknad | | :--- | :--- | | localStorage (default) | Bruker localStorage direkte | | none | Ingen persistering, surveyen vises hver gang |

Pakken eksporterer ferdige surveyer og builder-funksjoner for de vanligste brukscasene:

import {
  DEFAULT_SURVEY_RATING,
  DEFAULT_SURVEY_DISCOVERY,
  DEFAULT_SURVEY_SERVICE_FEEDBACK,
  createTopTasksSurvey,
  createTaskPrioritySurvey,
  createRatingSurvey,
  createDiscoverySurvey,
} from "@lumianalytics/survey";

| Preset / Builder | Beskrivelse | | :--- | :--- | | DEFAULT_SURVEY_RATING | Emoji-rating + valgfri fritekst | | DEFAULT_SURVEY_DISCOVERY | "Hva kom du hit for å gjøre?" + oppfølging | | DEFAULT_SURVEY_SERVICE_FEEDBACK | Tjenestevurdering med detaljer | | createRatingSurvey({...}) | Tilpasset rating med egne spørsmål og oppfølging | | createDiscoverySurvey({...}) | Tilpasset discovery-survey | | createTopTasksSurvey({tasks}) | Top Tasks (McGovern-metoden) | | createTaskPrioritySurvey({tasks}) | Task Priority / Long Neck-rangering |

Eksempel med builder:

const topTasks = createTopTasksSurvey({
  tasks: [
    { value: "apply", label: "Søke om sykepenger" },
    { value: "status", label: "Sjekke status" },
  ],
});

<LumiSurveyDock surveyId="top-tasks" survey={topTasks} transport={transport} />;

Rask tommelfingerregel for å velge riktig surveytype. Poenget er å få handlingsbare data med minst mulig friksjon for brukeren.

| Surveytype | Når bruke | Hva du får ut | Typiske fallgruver | | --- | --- | --- | --- | | rating | "Pulse" etter en konkret oppgave eller flyt | Trend over tid + (valgfri) årsak i fritekst | For generelt spørsmål, for mange spørsmål, for hyppig visning | | discovery | Utforskning: hva kom brukeren hit for å gjøre? | Frie tekstsvar + "fikk du gjort det?" + ev. blocker | For mye tekst, dårlig segmentering, samler identifikatorer i context | | topTasks | Måle suksess for kjerneoppgaver (McGovern) | Suksess/feil per oppgave + blocker-innsikt | For mange/få oppgaver, ikke randomisert rekkefølge, uklare oppgavenavn | | taskPriority | Strategisk: hva er viktigst å prioritere? (Long Neck) | Rangering av viktigste oppgaver (top N) | For få tasks, ikke randomisert, feil UI-variant for mange tasks | | custom | Når du må kombinere eller branch'e | Skreddersydd spørreflyt | Blir fort "for mye", vanskelig å sammenligne over tid |

Anbefaling: Start med rating eller discovery, og gå videre til topTasks/taskPriority når dere har en tydelig hypoteseliste.

Best practices

  • Hold det kort: 1–2 spørsmål er ofte nok (rating + valgfri tekst).
  • Still spørsmål om en konkret opplevelse ("etter du gjorde X"), ikke hele produktet.
  • Bruk progresjon: vis fritekst først etter at rating er valgt (visibleIf).
  • Bruk context.tags for segmentering (lav kardinalitet), og context.debug kun for feilsøking (høy kardinalitet).
  • Unngå identifikatorer i context (og ikke auto-collect pathname på dynamiske ruter).
  • Velg en stabil surveyId per flate/bruksmønster (ikke per deploy).

Go-live sjekkliste

  • Importer styling: @lumianalytics/survey/styles.css.
  • Implementer transport.submit som sender submission.transportPayload til din backend.
  • Sett riktig storageStrategy (localStorage / none).
  • Test ende-til-ende (innsending → dashboard).

LumiSurveyDock props

| Prop | Type | Påkrevd | Beskrivelse | | :--- | :--- | :---: | :--- | | surveyId | string | ✅ | Unik identifikator for surveyen (f.eks. soknad-kvittering) | | survey | LumiSurveyConfig | ✅ | Konfigurasjonsobjektet for spørsmålene | | transport | LumiSurveyTransport | ✅ | Objekt med submit-funksjon for innsending | | context | object | ❌ | Metadata/tags/debug for segmentering | | behavior | object | ❌ | Styrer åpning, lukking, cooldown og storage-strategi | | events | object | ❌ | Event-callbacks for sporing og livssyklus | | labels | object | ❌ | Tekster for UI-elementer (send-knapp, feilmeldinger) | | success | object | ❌ | Konfigurer suksess-visning (tittel, tekst, auto-lukk) | | style | object | ❌ | Visuell styling (posisjon, farger, classNames) |

Transport og payload

Widgeten sender submission.transportPayload til din backend. Payloaden er stabil og versjonert (schemaVersion: 1). Den inkluderer:

  • surveyId, surveyType, submittedAt, startedAt
  • answers: Normalisert struktur per spørsmål
  • context: tags/debug/auto-collectet miljøinfo

Backend-oppsett (token exchange, NAIS-tilgang) er beskrevet i Koble til backend.

Kontekst og personvern

Auto-collectes i browser:

  • viewport (bredde/høyde)
  • deviceType (mobile/tablet/desktop)
  • userAgent

url og pathname auto-collectes ikke som default. Hvis dere har statiske ruter uten identifikatorer kan dere opt-in:

<LumiSurveyDock behavior={{ collectLocation: true }} />

Hvis rutene kan inneholde ID-er, send heller en sanitert verdi:

<LumiSurveyDock context={{ pathname: "/sak/:id" }} />

Tags vs debug

  • context.tags: Lav kardinalitet, brukes til segmentering og grafer i dashboard.
  • context.debug: Høy kardinalitet, kun for detaljvisning av enkeltinnsendinger.

Events (hooks)

Registrer event-callbacks ved å sende et events-objekt til LumiSurveyDock.

const events = {
  onViewDock: (surveyId) => {},
  onAnswer: (questionId, value) => {},
  onSubmitStart: (submission) => {},
  onSubmitSuccess: (submission) => {},
  onSubmitError: (cause) => {},
  onValidationFailed: (missingQuestionIds) => {},
  onReset: () => {},
  onDismissalPersistFailed: (cause) => {},
};

Bruk logic når du faktisk må endre flyten (hoppe, skippe, eller avslutte tidlig). Hvis du bare vil vise/skjule oppfølgingsspørsmål, bruk heller visibleIf.

const surveyWithLogic = {
  type: "custom",
  questions: [
    {
      id: "rating",
      type: "rating",
      prompt: "Hvor fornøyd er du?",
      logic: [
        {
          condition: { field: "ANSWER", operator: "LT", value: 3 },
          action: { type: "JUMP_TO", targetId: "comment" },
        },
        {
          condition: { field: "ANSWER", operator: "GT", value: 2 },
          action: { type: "SUBMIT" },
        },
      ],
    },
    {
      id: "comment",
      type: "text",
      prompt: "Hva kan vi forbedre?",
    },
  ],
};

Feilsøking (vanlige problemer)

  • Survey dukker ikke opp: Sjekk at behavior.initialOpen ikke er satt til false, og at storageStrategy ikke skjuler den pga. cooldown.
  • Ingen data i dashboard: Verifiser at backend sender submission.transportPayload til riktig endepunkt.
  • Layout virker "tom": Sørg for at @lumianalytics/survey/styles.css er importert.
  • Dismissed-tilstand persisteres ikke: Sjekk at storageStrategy ikke er satt til "none".