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

@educspace/eval-element

v0.4.0

Published

Redistributable <exercise-element> web component: renders and grades educspace exercises on any web page.

Readme

@educspace/eval-element

<exercise-element> — the educspace exercise widget as a single script tag, usable on any web page.

<script type="module" src="/assets/eval-element.js"></script>

<div class="stv-exercise" data-exercise-id="…">
  <div class="stv-exercise-prompt">
    <p>Quel est le type de <code>3.14</code> ?</p>
  </div>
  <script type="application/json" class="stv-exercise-data">
    { … Question JSON … }
  </script>
</div>

Loading the script registers the element and upgrades every mount on the page. Nothing else is needed: no framework on the host page, no stylesheet to include, no configuration. demo/index.html is a complete working page.

The mount markup is what stevenson's exercise filter emits, but nothing here is stevenson-specific — any generator (or hand-written HTML) can produce it.

What it does

Renders one exercise of any supported kind (free text, single/multiple choice, ordered list, code, web, sql), runs it in the browser (Pyodide for Python, sql.js for SQL, a sandboxed iframe for web) and grades it client-side through @educspace/eval-engine.

Every verdict is also dispatched as an eds-graded DOM event (bubbling and composed), so a host page can observe results:

document.addEventListener('eds-graded', (e) => {
  const { correct, payload, meta, questionId } = e.detail;
});

Tracking (optional)

By default the element is anonymous and client-side, and makes no network calls. A page opts into tracking with two meta tags:

<meta name="questions-server" content="https://console.questions.educ.space" />
<meta name="questions-teacher" content="jmb" />
<!-- optional: where the student sees their sheets and results -->
<meta name="questions-me" content="https://me.questions.educ.space" />

With those present, a tracked exercise (see the mount contract below) shows a discreet strip: an invitation to log in, then the student's badge — solved or not, how many recorded attempts, and whether the statement has changed since. Every Check is declared to the server with the answer, the client's verdict, the version answered, and the time spent.

What it does not do is get in the way:

  • not logged in, no server configured, server unreachable → the exercise works exactly as it does untracked;
  • an attempt that cannot be sent is queued in localStorage and replayed on the next load;
  • an attempt refused by a policy (attempt cap, cooldown) still grades and shows its verdict — only the recording is skipped, with the server's explanation.

One login per browser per site: the token is shared by every exercise on every page of that site, and the whole page's badges are fetched in a single request.

With questions-me set, a logged-in student also gets a link to their own page of sheets and results, carrying the teacher slug so they never have to know it.

Mount contract

| | | | -------------------------- | ------------------------------------------------------------------ | | div.stv-exercise | the mount, replaced by an <exercise-element> | | .stv-exercise-prompt | prompt HTML, pre-rendered by the generator (Markdown, MathML) | | script.stv-exercise-data | the Question as camelCase JSON | | data-exercise-id | question UUID | | data-exercise-ref | author-chosen id — the identity a tracking server knows | | data-exercise-mode | tracked / assessed / pool; absent means anonymous | | data-exercise-version | hash of the exercise source, recorded with an attempt | | data-exercise-pool | the pool: which one this exercise belongs to, or a slot draws from | | data-exercise-slot | on a pool slot, the identity its draw is remembered against | | data-exercise-title | heading for a mount that has no statement to provide one |

Only the mount class and the payload are required; everything else is tracking metadata, and an exercise without it is anonymous: client-side, nothing recorded.

A mount in assessed or pool mode carries no payload: the statement lives on the server and is fetched once the student has logged in (an assessed exercise by id, a pool slot by drawing a member). Until then the box says why it is empty rather than pretending to be an exercise. A served statement comes as Markdown, rendered by the element; when the site that published it also sent the statement rendered through its own pipeline (promptHtml), that HTML is used instead — sanitised — so callouts, includes and the like look exactly as they do in the surrounding page. Assessed answers are withheld until the deadline passes, at which point the same exercise is served whole and becomes its own correction, and it is the server's verdict — never the browser's — that the student is shown.

Theming

The element renders into a shadow root: the host page's CSS cannot reach in and its own cannot leak out. Theming therefore goes through CSS custom properties, which do cross the boundary:

exercise-element {
  --eds-exo-accent: #7c3aed;
  --eds-exo-surface: #faf5ff;
  --eds-exo-radius: 14px;
}

The full list is documented at the top of src/exercise/styles.ts.

Coexisting with other bundles

The element ships its own Angular runtime, so a page may well end up running two Angular applications (the site's own template and this one). That is fine under three conditions, all of which this package satisfies:

  1. Zoneless. zone.js patches globals once; two copies fight. This bundle uses provideZonelessChangeDetection() and so must any other bundle on the page.
  2. One owner per custom-element name. The registry is page-global and a name defines once. This bundle owns exercise-element and guards its define(), so loading it twice is harmless — but no other bundle may define that name.
  3. Style ownership. Shadow DOM, per above. Tailwind (or any utility CSS from the host page) does not cross into the element, and must not be relied on.

Build

npm install
npm run build     # dist/eval-element.js

The build refuses to emit more than one script: the element must stay a single file, so avoid dynamic import() in src/. Monaco is the deliberate exception — it is fetched from a CDN on demand, only when a code/sql/web exercise is actually present, and its stylesheets are copied into the shadow root (src/exercise/monaco-loader.ts).