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

@campminder/campminder-shell

v0.3.2

Published

> Published on public npm. Install and render: [`docs/consuming-the-shell.md`](../../docs/consuming-the-shell.md). > Working in here: [`CONTRIBUTING.md`](../../CONTRIBUTING.md).

Readme

@campminder/campminder-shell

Published on public npm. Install and render: docs/consuming-the-shell.md. Working in here: CONTRIBUTING.md.

The app shell for the Campminder portal. One portal, one shell.

This is the container Kevin estimated at roughly 5% of the code — it assembles the chrome and owns layout. It is deliberately ERP-specific; that is its whole job. The generic, composition-agnostic components become specific again here.

The rule it enforces

Components render themselves; the shell owns layout. No component may touch document.body, document.head, or query for a sibling.

Concretely: the grid in app-shell.ts computes its tracks from which slots are actually filled. A shell with no side nav has no side-nav column, so BodyPaddingController's padding-left: 168px !important has no successor here — it was solving a problem the shell no longer has.

Usage

<campminder-app-shell brand-color="#5B2D8E">
  <cm-super-user-banner slot="banner"></cm-super-user-banner>
  <cm-side-nav slot="side-nav"></cm-side-nav>
  <cm-top-bar slot="top-bar"></cm-top-bar>
  <main>…page content…</main>
</campminder-app-shell>

Every slot is optional. Omitting one is a supported composition, not a degraded state — which is the property apps/harness exists to prove.

The menu tree

src/tree/campminder.json is the Campminder portal's menu tree, three levels deep (category → sub-category → leaf), typed by MenuCategory / MenuSubCategory / MenuLeaf and validated against a JSON Schema — both of which now come from @campminder/menu-tree on public npm, published by the nav Domain System and resolved through the package manager. src/tree.ts re-exports the types so this package's public surface is unchanged; there is no local copy of either the types or the schema.

That split is ADR D4 (the menu data model belongs to nav) plus D5 (validate against a published schema rather than a copied one). What stays here is the instance: campminder.json is one ERP's menu, which D6 puts in the app shell, along with FilterContext and the derivation tooling — Campminder-specific by construction. nav is ERP-agnostic and depends on portals in no direction.

One thing to know before hand-writing a fixture: the published MenuCategory requires sortOrder, while sub-categories and leaves keep it optional (this tree omits it on 1 of 48 sub-categories and 214 of 249 leaves). Nothing in portals reads sortOrder — it is carried faithfully for the host, never sorted on here.

Where it actually comes from

The canonical menu is navigation-data.json, an embedded resource in the Core .NET assembly, read by NavigationAccessor's static constructor, filtered by NavigationBuilder, base64-encoded and handed to the component as a data= attribute. The authority for fields and gates is therefore the C# — MenuItem in NavigationAccessor.cs and the Matches* calls in NavigationBuilder.cs.

The first port derived its types from core-js's apps/side-nav/src/data/navigation-data.schema.json instead. That schema is stale, and copying it cost us two defects: a phantom client rule (the field is dead upstream — zero instances, no gate) and a silently dropped feature_keys gate (live, and evaluated). Both corrected 2026-09-01.

Regenerate and compare with — both run from the repo root, and both take the host path as an argument so the repo stays uncoupled from any checkout layout:

pnpm derive:menu-tree <path-to-navigation-data.json> --diff packages/campminder/src/tree/campminder.json
pnpm check:host-contract <path-to-Core/Websites/CampMinder/navigation>

Two things the tree cannot represent

  • Terminology tokens. Upstream labels carry {{Bunk:Proper:Default}}, resolved per client by Terms.Factory.ConvertString. They are stored verbatim. The first port substituted them to the literal word "Group", which baked one camp's vocabulary into every client's menu. A test now fails if any token is resolved away.

  • Database-sourced items. DatabaseMenuItems.GetCustomReportMenuItems() and GetPhoneReservationMenuItems() inject per-client rows at request time, and NavigationBuilder.cs:104-111 assigns them with =, not += — so a static leaf in Reporting > Custom Reports or Reporting > Phone Reservations is replaced at request time, not merged with. Both sub-categories are therefore deliberately empty of leaves here, matching upstream, and a test pins that. The four Phone Res: * leaves that used to sit here were hard-coded copies from the base64 fixture; they were removed 2026-09-02. Do not re-add them.

    Whether Phase 1 needs those leaves populated for a real user is an open product question, not a gap in the tree — a static tree cannot answer it, and answering it yes means Phase 1 needs a runtime call after all. See the plan's open questions.

The drift guard

src/tree/drift.test.ts compares every field of every node against src/tree/upstream-derived.json, and any difference must be listed in src/tree/known-deltas.json to pass — as must its absence, so a delta that has been fixed cannot be left lying in the file. Both JSON files are test-only data; nothing at runtime imports them and index.ts exports only campminder.json.

known-deltas.json is currently empty of deltas, and that is the goal state — as of 2026-09-02 the shipped tree is field-for-field identical to what tools/derive-menu-tree.mjs produces from the host. An empty file is not a weaker guard: dropping a single sortOrder, resolving a terminology token, or re-adding one database-sourced leaf each turn the suite red, verified by mutation.

upstream-derived.json is committed rather than derived in CI because CI has no Core checkout. That is the guard's one real limit: it catches hand-edits to the shipped tree, not upstream changes. Refreshing the fixture is a local act, and known-deltas.json records the Core commit the current one came from. Tracked as ADO #26026 (Floor 5C) — #26019 raised it and closed as moved on 2026-09-02 once nav became ours to build. Do not read the empty deltas file as that limit being closed. Deleting this paragraph is one of #26026's acceptance criteria; until then it stands.

The schema's placeholder home here is gone — resolved 2026-09-08 by Floor 5B. ADR D4 gives the menu data model to the nav Domain System and ADR D5 requires the tree be validated against a published JSON Schema; nav was an empty repo with nowhere to publish to, so packages/campminder/src/tree/ held the copy in the meantime. Kevin, on 2026-08-29: "That stuff actually does belong to the domain system."

Both halves have now moved. menu-tree.schema.json and the structural types (which by then had drifted to packages/components/src/menu-tree.ts) are published as @campminder/menu-tree and consumed here through the package manager. There is no local copy of either.

campminder.json itself does not move, ever. nav is mandated ERP-agnostic; this file is one ERP's menu, and ADR D6 puts each portal's tree instance in its app shell. The same test keeps tools/derive-menu-tree.mjs here too — it reads CampMinder's navigation-data.json. nav gets the model and the generic guard; the Campminder-specific derivation stays.

What it does not do

  • It does not fetch. The tree is committed configuration, passed in as tree.
  • It does not filter. Filtering is a pure function of context the host already holds — enabled feature keys, client feature ids, version branch. FilterContext types that contract; no implementation exists yet (Floor 6). Note the host also gates on AttendanceValidator.HasAccessTravelDateOnly(), which is imperative and cannot be expressed as a rule. Whether the evaluator therefore needs host-supplied overrides is provisional — Kevin is re-evaluating the gate (2026-09-01); it may be retired or moved upstream instead. See the plan's Floor 6 before designing around it.
  • It does not own the components. They are slotted, and it never reaches into them.

Phase 1 scope

Left nav is the priority. The top bar is secondary and may ship without a search widget purely to reserve the space. Search is not being rebuilt, and per-client terminology substitution is deferred.

carveOutOrigin is optional for exactly that reason — Phase 1 is expected to need no runtime calls at all.