Skip to content

Latest commit

 

History

History
75 lines (54 loc) · 6.12 KB

File metadata and controls

75 lines (54 loc) · 6.12 KB

StackOne Hub — Claude instructions

@stackone/hub is a React component library that ships an embeddable integration picker. It's bundled with Rollup and consumed in three forms: ESM, CJS, and a self-contained web component IIFE.

Commands

Task Command
Build all bundles npm run build
Vite dev sandbox (port 3001) npm run dev
Next.js SSR sandbox (port 3002) npm run dev:nextjs
First-time Next sandbox setup npm run dev:nextjs:setup
Lint npm run lint (Biome)
Auto-fix lint/format npm run lint:fix and npm run code:format:fix
Regenerate Relay artifacts npm run relay

Always run npm run lint before committing. Biome is the only linter — don't add ESLint/Prettier configs.

Project layout

  • src/index.ts — public entry, exports StackOneHub
  • src/StackOneHub.tsx — top-level component (carries the 'use client' directive)
  • src/Hub.tsx — inner component, switches on mode
  • src/modules/integration-picker/ — main feature
  • src/shared/ — error boundary, http client, feature flags, queries
  • src/WebComponentWrapper.tsx — separate entry built into dist/webcomponent.js
  • dev/vite/ — Vite-based dev sandbox (own package.json, hub linked via file:../..)
  • dev/nextjs/ — Next.js 15 + React 19 App Router SSR sandbox (own package.json, hub linked via file:../..)
  • rollup.config.mjs — three bundle outputs (ESM main, CJS main, IIFE webcomponent) plus a .d.ts rollup
  • dist/ — build output, gitignored, the only thing published

SSR / Next.js constraints

The package is consumed by SSR frameworks (Next.js App Router). When changing src/, keep these invariants intact:

  1. 'use client' directive on the bundle. It's injected by output.banner in rollup.config.mjs and survives minification because terser is configured with compress: { directives: false }. If you change the rollup config, verify head -c 30 dist/index.esm.js still starts with "use client";.
  2. No window/document/localStorage access during render. Anything that touches the DOM must live inside useEffect, an event handler, or be guarded with typeof window !== 'undefined'. Render-time access (including useMemo and module-scope code) breaks SSR.
  3. customElements.define is module-scoped in WebComponentWrapper.tsx and is guarded with typeof window !== 'undefined' && typeof customElements !== 'undefined' plus a customElements.get check. Keep the guards if you edit that file.
  4. Theme application mutates <html>. applyTheme/applyLightTheme/applyDarkTheme from @stackone/malachite set CSS custom properties on document.documentElement. This requires consumers to add suppressHydrationWarning to their <html> tag (documented in README). Don't move these calls out of useEffect.
  5. package.json sideEffects field marks only ./dist/webcomponent.js as side-effecting so bundlers can tree-shake the React entry. Keep it that way.
  6. Single React instance. react/react-dom/react-hook-form are peer deps and the bundle imports them at runtime — two copies in the consumer's tree breaks hooks (Invalid hook call). Standard npm install hoists React and is fine. Monorepos, pnpm without hoist, and file:/link: deps may require explicit deduping by the consumer. The README's "Invalid hook call — duplicate React" section documents fixes.
  7. Every JS bundle must apply replaceValues from rollup.config.mjs. It stamps __HUB_VERSION__ with the package version, which src/shared/version.ts sends as the x-hub-version header on every request. A new bundle target that omits it still builds clean and silently reports x-hub-version: unknown. npm run verify:build (wired into both CI workflows) is the guard — add any new bundle to the list in scripts/verify-build.mjs.

Build output

File Format Notes
dist/index.esm.js ESM Has 'use client' banner
dist/index.js CJS Has 'use client' banner
dist/index.d.ts TS declarations Generated by rollup-plugin-dts
dist/webcomponent.js IIFE React + ReactDOM bundled in, registers <stackone-hub>

Pre-existing build warnings about crypto / vm Node built-ins (from @stackone/utils and jsonpath-plus) are noisy but harmless for the React bundles — only the webcomponent IIFE actually needs them and they're never reached at runtime in a browser.

Dev sandboxes

Both sandboxes are now their own npm packages and consume the hub via "@stackone/hub": "file:../..", so they exercise the actual built dist/ (including the 'use client' banner). Edit hub source, run npm run build from the repo root, and both sandboxes pick up the new bundle automatically — no reinstall needed.

  • Vite sandbox (dev/vite/): port 3001. Run via npm run dev from the repo root. First-time setup is npm run dev:setup (builds the hub, then npm install inside dev/vite/).
  • Next.js sandbox (dev/nextjs/): port 3002. Run via npm run dev:nextjs. First-time setup is npm run dev:nextjs:setup. Use this one to validate SSR behaviour.

Vite sandbox needs resolve.dedupe for react, react-dom, react-hook-form because the symlinked hub at node_modules/@stackone/hub resolves to a directory with its own node_modules — two React copies otherwise. The dev/vite/vite.config.ts is already set up correctly. If you add a new peer dep to the hub, add it to the dedupe list.

If you add a new public prop to StackOneHub, update both dev/vite/main.tsx and dev/nextjs/app/HubWrapper.tsx so the props stay testable in both sandboxes.

Versioning / publishing

Releases use release-please-config.json. Don't bump version in package.json manually — release-please handles it.

Style conventions

  • No comments in code unless the why is genuinely non-obvious. Names should carry the intent.
  • TypeScript strict mode is on; don't reach for any. Internal types live in src/types/, feature-specific types live in their feature folder (src/modules/integration-picker/types.ts).
  • 4-space indent (Biome enforces).
  • Imports are sorted by Biome — let the formatter do it.