@codaco/interview

中文文档
11.0.0发布于 2026/10/21MITTypeScriptES Module构建来源已验证近 30 天下载 3765

Network Canvas interview engine — Shell component, session contract.

原始使用文档
文档目录20 节
  1. Install
  2. Tailwind
  3. Vitest / jsdom
  4. The Shell component
  5. Why is currentStep host state?
  6. Shell props
  7. Toast viewport
  8. Building an InterviewPayload server-side
  9. Sparse entity attribute flow
  10. Public API reference
  11. Components
  12. Schemas + helpers
  13. Synthetic data
  14. Public types
  15. Theming & DOM scope
  16. Testing the integration
  17. Unit / component tests (Vitest + jsdom)
  18. End-to-end (Playwright)
  19. What lives in this package, what doesn't
  20. License

@codaco/interview

The Network Canvas interview engine — packaged so any React host can embed the survey UI a participant interacts with. Owns the Redux store, stage navigation, all 17 interface implementations (NameGenerator, Sociogram, CategoricalBin, Geospatial, …), the dialog system, the toast system, and the design tokens. The host owns the network calls (sync, finish, asset URL resolution) and where the participant currently is in the protocol.

The intended hosts are Fresco (Next.js) and the new Architect preview mode (Vite) — but the package has no awareness of either.


Install

pnpm add @codaco/interview

Peer dependencies (you most likely already have these):

{
  "@codaco/fresco-ui": "^2.5.4",
  "@codaco/protocol-validation": "^11.5.0",
  "@codaco/shared-consts": "5.0.0",
  "@codaco/tailwind-config": "^1.0.0-alpha.11",
  "immer": "^11.1.4",
  "motion": "^12.38.0",
  "react": "^19.2.5",
  "react-dom": "^19.2.5",
  "tailwindcss": "^4.2.4",
}

Tailwind

The package assumes the host has a Tailwind v4 build plugin wired up (@tailwindcss/vite for Vite, @tailwindcss/postcss for Next.js / any PostCSS pipeline). Each CSS file in the chain owns one concern, and consumers import them in order:

/* styles/globals.css */
@import '@codaco/tailwind-config/fresco.css'; /* Tailwind v4 + theme + plugins + fonts */
@import '@codaco/fresco-ui/styles.css'; /* @source glue for fresco-ui's classes */
@import '@codaco/interview/styles.css'; /* @source glue for interview's classes */

Do not also @import "tailwindcss" yourself. That import lives inside @codaco/tailwind-config/fresco.css; adding it again loads Tailwind's runtime twice and produces duplicate / conflicting utilities.

Each of @codaco/fresco-ui/styles.css and @codaco/interview/styles.css is a tiny file containing only a @source "./**/*.{js,ts,tsx}" directive scoped to that package's own module files. The glob resolves relative to wherever the imported styles.css sits — the package's src/ tree when consumed from source (as workspace hosts do), or the mirrored dist/ copy for an npm install. With both imported, Tailwind's class scanner walks both packages and emits every utility class the components reference. Hosts no longer need to write @source '../node_modules/...' lines themselves.

@codaco/tailwind-config/fresco.css bundles both the default and interview theme variants. Shell renders <main data-theme-interview> with a portal container so dialogs/popovers stay inside the themed subtree — no host-side setup required. See Theming & DOM scope below.

Vitest / jsdom

Node has no CSS loader, so any vitest test that imports from @codaco/interview (even just the schemas) needs Vite to process the package, otherwise you get "TypeError: Unknown file extension .css". Inline the package on every project that uses jsdom:

// vitest.config.ts
export default defineConfig({
  test: {
    server: {
      deps: { inline: ['@codaco/interview'] },
    },
  },
});

The Shell component

import {
  createDebouncedSyncHandler,
  Shell,
  type InterviewPayload,
} from '@codaco/interview';

A complete minimal host (TypeScript / Next.js App Router style — the shape is identical for any other React framework):

'use client';

import {
  createDebouncedSyncHandler,
  Shell,
  type AssetRequestHandler,
  type FinishHandler,
  type InterviewPayload,
  type SyncHandler,
} from '@codaco/interview';
import { useCallback, useMemo, useState } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';

export default function InterviewClient({
  payload,
  resolveAssetUrl,
}: {
  payload: InterviewPayload;
  resolveAssetUrl: (assetId: string) => Promise<string>;
}) {
  const router = useRouter();
  const params = useSearchParams();

  // currentStep is HOST state — the package never owns it. Keep it in
  // useState, nuqs, a context, the URL, wherever fits the host. The
  // package re-renders when the prop changes.
  const [currentStep, setCurrentStep] = useState<number>(() =>
    Number(params.get('step') ?? payload.session.currentStep),
  );

  const onStepChange = useCallback(
    (step: number) => {
      setCurrentStep(step);
      const next = new URLSearchParams(params.toString());
      next.set('step', String(step));
      router.replace(`?${next.toString()}`);
    },
    [params, router],
  );

  // Persist state on every reducer commit. Receives the full session
  // payload — POST it to your server, write to IndexedDB, anything.
  //
  // The engine offers every change as it happens and never batches on your
  // behalf: only you know what one write costs. A local write can take them
  // all; a network request usually should not, so wrap it in
  // `createDebouncedSyncHandler` (below) rather than posting per answer.
  const onSync: SyncHandler = useMemo(
    () =>
      createDebouncedSyncHandler(
        async (interviewId, session) => {
          await fetch(`/interview/${interviewId}/sync`, {
            method: 'POST',
            body: JSON.stringify(session),
          });
        },
        { waitMs: 3000 },
      ),
    [],
  );

  // Called when the participant clicks Finish on the FinishSession
  // stage. Receives an AbortSignal so you can cancel any in-flight work
  // if the user backs out.
  const onFinish: FinishHandler = useCallback(
    async (interviewId, signal) => {
      await fetch(`/interview/${interviewId}/finish`, {
        method: 'POST',
        signal,
      });
      router.push(`/interview/${interviewId}/complete`);
    },
    [router],
  );

  // Stages reference protocol assets by ID. The package calls this
  // exactly when a stage needs a URL — return a same-origin URL,
  // pre-signed S3 URL, blob: URL, whatever your storage uses.
  const onRequestAsset: AssetRequestHandler = useCallback(
    (assetId) => resolveAssetUrl(assetId),
    [resolveAssetUrl],
  );

  return (
    <Shell
      payload={payload}
      currentStep={currentStep}
      onStepChange={onStepChange}
      onSync={onSync}
      onFinish={onFinish}
      onRequestAsset={onRequestAsset}
    />
  );
}

Why is currentStep host state?

So the host can drive it however it likes — nuqs URL params, browser history, a stepper UI, deep links from email, server-rendered initial position. The package reads currentStep and emits onStepChange for every navigation; it does not maintain its own copy.

This is also what allows the host to mount Shell once but render different stages without re-creating the Redux store: only the currentStep prop changes between renders.

Shell props

PropTypeRequiredNotes
payloadInterviewPayloadyes{ session, protocol } — see the type for shape. The store is created once per payload.session.id; pass a stable reference.
currentStepnumberyesThe stage index the participant is on. Owned by the host.
onStepChange(step: number) => voidyesFired whenever the participant navigates. The host should mirror step into its own state.
onSync(id, session, opts) => PromiseyesCalled after every Redux commit — the engine does not batch. Persist however you like; wrap in createDebouncedSyncHandler if writes are expensive. opts.immediate marks writes that must not be deferred (exit, finish); opts.unloading additionally marks the ones the document may not survive (hidden, pagehide).
onFinish(id, AbortSignal) => PromiseyesCalled from the FinishSession stage. The signal aborts if the user navigates away mid-flight.
onRequestAsset(assetId) => Promise<url>yesResolve a protocol asset to a URL. Called lazily as stages mount.
analyticsInterviewAnalyticsMetadatayesHost metadata attached as super-properties on every event: installationId (anonymous host UUID), hostApp (e.g. "Fresco"), hostVersion?.
posthogClientA posthog-js client (needs capture, captureException, register)noPre-initialised PostHog client. When provided, the package emits events through it without modifying its config. When absent, the package lazy-initialises its own named instance against ph-relay.networkcanvas.com.
disableAnalyticsbooleannoWhen true, all event emission is suppressed (no posthog-js import). Default false. Use for E2E and synthetic-interview runs.
finishConfirmationDescriptionReactNodenoHost-specific explanation shown in the finish confirmation dialog. Defaults to localized neutral guidance that does not promise responses are immutable. A subscribed message component can keep a host override responsive to language changes.
requestedLocalestring | readonly string[] | nullnoA user preference, resolved host locale, or ordered locale requests. The package negotiates against its own supported interface languages; unmatched requests use English.
localePreferencestring | nullnoOptional controlled menu choice, paired with onLocaleChange. A string selects the best supported match; null follows requestedLocale. Omit it to keep menu selection local to the package.
onLocaleChange(locale: string | null) => voidnoCalled after a menu selection so the host can persist it. null means follow the host request again.
catalogInterviewCatalognoThe interface language's messages from loadInterviewCatalog, given the same requestedLocale and localePreference. Lets the interview render, and hydrate, without waiting for that language to download. Used only while it matches the negotiated language.
allowLanguageSelectionbooleannoShow the interface language chooser in the settings menu. Defaults to true.
flags{ isE2E?, isDevelopment? }noisE2E: true exposes window.__interviewStore for Playwright fixtures. isDevelopment: true enables redux-logger.

The package replaces a previous onError callback with internal posthog.captureException calls; render errors and asset-load failures are reported via the resolved analytics client (or suppressed when disableAnalytics is true). The host does not need to wire its own error sink.

Interface language

Pass the user's preference or your host's already negotiated locale as requestedLocale. The package owns its registry and messages: it currently supports en, en-GB, es, zh-Hans, zh-Hant, de, nl, pt-BR, it, and fr, matches regional requests such as es-MX to es, zh-CN and zh-SG to zh-Hans, zh-TW, zh-HK and zh-MO to zh-Hant, de-AT to de, nl-BE to nl, pt or pt-PT to pt-BR, it-CH to it, and fr-CA to fr, and falls back to en for unsupported or malformed requests. An array expresses requests in preference order. No host provider or catalog is required. Each language's messages are a separate chunk, loaded when an interview first shows that language; English needs none. A Shell mounting in a language that has not loaded yet shows a spinner on the interview's surface until it has, rather than render English first, and a later switch keeps the current language on screen until the new one is ready. An offline host keeps every language available by precaching every chunk of its build, as a PWA's service worker does.

A host can take that download off the interview's path with loadInterviewCatalog(requestedLocale, localePreference) from @codaco/interview/catalog, which negotiates exactly as Shell does and resolves to { locale, messages }. The entry carries no React, so a server can import it: a server-rendered host awaits it and passes the result as catalog, and the interview renders and hydrates in that language with no spinner and no request. A client host calls it without awaiting while it prepares the payload; the Shell then finds the language already loaded.

The setting controls package-provided buttons, menus, validation, accessibility labels, help, and stage controls. Protocol-authored titles, prompts, labels, options, research values, and identifiers are passed through unchanged. Protocol content localization is a separate schema concern.

The menu can temporarily override the host request. Its Automatic option clears that override; a new host request also takes effect immediately. A host that persists menu changes can use onLocaleChange and pass its newly resolved locale back as requestedLocale. To mirror a saved explicit/automatic choice in the menu, also pass localePreference: a string takes precedence and is matched against the package registry; an unmatched or malformed preference falls through to requestedLocale. null follows requestedLocale, and omitting it keeps package-local menu state. This lets Automatic clear a saved preference directly after a reload. The package itself reads no browser preference or storage. Locale changes preserve the mounted interview, pending form input, navigation and answers. The package sets lang and dir on its own region and leaves the host document's language to the host.

Hosts rendering exported controls outside Shell, such as an inline ProtocolField preview, can use InterviewI18nProvider from @codaco/interview with the same requestedLocale contract and package-owned catalogs. Unlike Shell, it brings no Suspense boundary of its own: its first render in a language that has not loaded yet suspends, so render it under one. Hosts that already own a provider can instead add interviewCatalogLoaders from @codaco/interview/locales to their app's catalog source. Without a provider, standalone controls use their English defaults.

Analytics

The interview package emits PostHog events directly — there is no onError-style host bridge. Three operating modes:

  1. Host-supplied client — pass posthogClient; the package emits via the host's instance, with distinct_id overridden per event to the interview id. Host instance config (autocapture, identify, session recording) is the host's responsibility.
  2. Own instance — omit posthogClient; the package lazy-imports the posthog-js/dist/module.no-external build (no remote script loader, so it runs under a script-src 'self' policy) and inits a named instance ('@codaco/interview') against https://ph-relay.networkcanvas.com.
  3. Disabled — pass disableAnalytics={true}; no events emitted, no posthog-js import.

PII contract: events never include protocol-network data, protocol-author content (stage labels, prompt text, codebook labels, asset names), or participant input (form values, free-text, alter labels, search queries, passphrases). Events include only structural identifiers (stage type/index, prompt index, random node/edge UUIDs), codebook internal ids (e.g. "person", "friend"), counts, durations, and package-defined discriminators.

The full event taxonomy lives at docs/superpowers/specs/2026-05-05-interview-analytics-design.md.

Toast viewport

Shell mounts its own Base UI toast provider and viewport inside the themed, localized interview region. Hosts do not need another interview viewport. This keeps notification text and accessible names responsive to the interview menu's language, including when the host uses a different language.

Each Shell keeps its validation notifications independent from other mounted interviews. An app-level toast provider can remain separate for host-owned notifications outside the interview.


Building an InterviewPayload server-side

The package never reaches into the host's database. Your server hands over a fully resolved { session, protocol } object, with all asset:// URLs already mapped to the IDs onRequestAsset will be called with.

import {
  type InterviewPayload,
  type ProtocolPayload,
  isValidAssetType,
} from '@codaco/interview';
import { hashProtocol } from '@codaco/protocol-validation';

export async function loadInterviewPayload(
  interviewId: string,
): Promise<InterviewPayload> {
  const interview = await db.interview.findUniqueOrThrow({
    where: { id: interviewId },
    include: { protocol: true },
  });

  // protocol assets come from your DB / object store — flatten to the
  // shape the package consumes
  const assets = interview.protocol.assets
    .filter((a) => isValidAssetType(a.type))
    .map((a) => ({
      assetId: a.id,
      name: a.name,
      type: a.type,
      ...(a.value ? { value: a.value } : {}), // apikey assets only
    }));

  const protocol: ProtocolPayload = {
    ...interview.protocol,
    // Content hash of { codebook, stages }. Computed at protocol-import time
    // by `hashProtocol` from @codaco/protocol-validation; forwarded here as a
    // super-property on every analytics event.
    hash: interview.protocol.hash ?? hashProtocol(interview.protocol),
    importedAt: interview.protocol.importedAt.toISOString(),
    assets,
  };

  return {
    session: {
      id: interview.id,
      // … the rest of SessionPayload — see the type for the full shape
      network: interview.network ?? createInitialNetwork(),
      currentStep: interview.currentStep ?? 0,
      stageMetadata: interview.stageMetadata,
      // …
    },
    protocol,
  };
}

createInitialNetwork() returns the canonical empty network with an ego node already initialised — call it once when you create a new interview record so subsequent loads pass schema validation.

A host that holds the validated protocol document itself, rather than flattened database records, builds the protocol half with currentProtocolToPayload(protocol, { id, importedAt }) from @codaco/interview/contract. Pass the same id and importedAt every time the same protocol is loaded.


Sparse entity attribute flow

Interview stores entity attributes as sparse records. An own attribute key always has a defined VariableValue; a missing key means the variable is unset. false, 0, '', and [] are defined responses and remain present.

flowchart LR
  Form[Mounted form fields] --> Adapter[Coerce values and build patch]
  Direct[Direct participant edit] --> Patch[AttributePatch: set and unset]
  Adapter --> Patch
  Patch --> Validate[Validate keys and overlap]
  Validate --> Apply[Apply attributes and secure metadata]
  Protocol[Protocol-derived prompt attributes] --> Apply
  Apply --> Network[Sparse session.network]
  Network --> Sync[onSync session]
  Sync --> Persistence[Host persistence]
  Persistence --> HostParse[Host read via NcNetworkSchema]
  HostParse --> Payload[Shell payload]
  Payload --> Network
  Persistence --> ExportParse[Exporter parse via NcNetworkSchema]
  ExportParse --> Output[CSV and GraphML]

The internal AttributePatch contract separates setting defined values from removing properties:

type AttributePatch = Readonly<{
  set: Readonly<Record<string, VariableValue>>;
  unset: readonly string[];
}>;

Form submission first coerces values to their protocol variable types. The adapter then considers exactly the mounted field names: each defined value goes to set, each undefined value goes to unset, and unmounted fields are ignored. If any mounted defined value is not a VariableValue, conversion fails as a whole and no partial patch is dispatched.

Creation and editing deliberately use different parts of that result:

  • Creating an entity writes only set, so unanswered fields are omitted.
  • Editing applies both set and unset, so clearing a mounted field deletes that key while attributes outside the form remain unchanged.
  • Direct participant edits, such as bins and layout controls, produce the same patch shape instead of writing nullish sentinels.

The node, edge, and ego update thunks validate that participant-edit patch keys belong to the applicable codebook definition and reject a key that appears in both set and unset. Node and edge creation apply the same key check to their initial attributes; controlled roster and pedigree imports can explicitly allow external node attributes. Validation completes before the fulfilled reducer can mutate session state. Prompt-membership patches are derived directly from the protocol's additionalAttributes, so reducers apply those trusted keys without repeating patch validation.

The shared applicator produces new attribute and secure-metadata maps for both validated and protocol-derived patches. An unset removes the attribute and its matching _secureAttributes entry in the same reducer transition; an empty secure-metadata map collapses to undefined.

Network Composer undo history is presence-sensitive. For every touched key, an inverse patch restores a prior own value with set, but restores prior absence with unset. This distinction is required when a defined empty value such as [] is edited and then undone.

onSync hands the host the sparse session after reducer commits. Hosts parse stored or received networks with NcNetworkSchema before constructing the next Shell payload; this accepts nullish input entries but emits sparse output. @codaco/network-exporters repeats that parse per session before formatting, so persistence and export do not reintroduce nullish attribute values.


Public API reference

Everything below is exported from '@codaco/interview'. Additional public subpaths expose the contract, protocol schema version, locale catalogs, the interview catalog loader (@codaco/interview/catalog) and styles; host code should not reach into package internals.

Components

  • Shell — the runtime
  • InterviewI18nProvider — package locale boundary for exported controls used outside Shell

Schemas + helpers

  • StageMetadataSchema — Zod schema for session.stageMetadata. Use it to validate state restored from your database before passing it back into a payload.
  • createInitialNetwork() — empty network with ego seeded. Call once per new interview.
  • isValidAssetType(type) — type predicate for ResolvedAsset.type.
  • getNodeLabelAttribute(variables, attributes) — pick the variable whose value should be displayed as a node's label. Used by sibling packages (e.g. @codaco/network-exporters) so exports use the same labelling logic the UI does.

Synthetic data

  • generateNetwork(codebook, stages, options?) — deterministic network generator that walks a protocol's stages and produces nodes, edges, ego attributes, and stage metadata. Use it for storybook fixtures, load tests, and the synthetic preview mode.
  • GenerateNetworkOptions, GenerateNetworkResult — companion types.

Public types

type InterviewPayload = { session: SessionPayload; protocol: ProtocolPayload };

type SessionPayload = SessionState; // the Redux session shape
type ProtocolPayload = Omit<CurrentProtocol, 'assetManifest'> & {
  id: string;
  importedAt: string; // ISO
  assets: ResolvedAsset[];
};

type ResolvedAsset = {
  assetId: string;
  name: string;
  type: 'image' | 'video' | 'audio' | 'network' | 'geojson' | 'apikey';
  value?: string; // apikey only
  source?: string; // the file's name in the protocol, used for MIME type and CSV/JSON decisions
};

type SyncOptions = {
  // Do not defer this write.
  immediate: boolean;
  // The document is being hidden or unloaded and may never run script again:
  // use a transport that outlives it, and do not queue behind a request that
  // will die with it. Always accompanied by `immediate`.
  unloading: boolean;
};
type SyncHandler = (
  interviewId: string,
  session: SessionPayload,
  options: SyncOptions,
) => Promise<void>;

// Batching is the host's decision. This wraps a handler so ordinary changes
// are rate-limited to one write per `waitMs` carrying the newest state, while
// `immediate` writes go out at once.
function createDebouncedSyncHandler(
  write: SyncHandler,
  options: { waitMs: number },
): SyncHandler;
type FinishHandler = (
  interviewId: string,
  signal: AbortSignal,
) => Promise<void>;
type AssetRequestHandler = (assetId: string) => Promise<string>;
type ErrorHandler = (error: Error, ctx?: Record<string, unknown>) => void;
type StepChangeHandler = (step: number) => void;

type InterviewerFlags = {
  isE2E?: boolean;
  isDevelopment?: boolean;
};

Theming & DOM scope

Shell renders a single <main data-theme-interview> element. This is both the stable selector for tests / e2e fixtures and the wrapper that activates the interview theme: descendants pick up the dark palette, Nunito typography, and responsive root font-size automatically.

Shell also provides a portal container (via <PortalContainerProvider> from @codaco/fresco-ui/PortalContainer) so dialogs, popovers, dropdowns, tooltips, toasts, selects, and comboboxes opened from inside the interview render into a node inside the themed subtree — they inherit the interview palette automatically rather than portaling to document.body.

If you render interview-themed UI outside of Shell (e.g. a "thank you" page after the interview ends), wrap that UI with <ThemedRegion theme="interview"> from @codaco/fresco-ui/ThemedRegion:

import { ThemedRegion } from '@codaco/fresco-ui/ThemedRegion';

<ThemedRegion theme="interview">
  <ThankYouPage />
</ThemedRegion>;

<ThemedRegion> and its ancestors up to <body> must not have transform, filter, perspective, or contain set — these create new containing blocks for fixed-positioned descendants and would break modal/popover positioning.


Testing the integration

Unit / component tests (Vitest + jsdom)

See the Vitest / jsdom note above — inline @codaco/interview in your config so the CSS side effect resolves cleanly.

End-to-end (Playwright)

For e2e you usually want a deterministic stand-in for the host's data layer. The package supports this through the isE2E flag, which exposes the live Redux store as window.__interviewStore so fixtures can read network state without hitting your database:

<Shell {...props} flags={{ isE2E: true }} />

The package's own e2e suite (in this repo) uses a Vite host that implements window.__test hooks for installProtocol / createInterview / reset, runs in the official mcr.microsoft.com/playwright image for font-rendering determinism, and asserts against per-stage screenshots in e2e/visual-snapshots/{chromium,firefox,webkit}-matrix/. Use it as a reference for wiring your own e2e setup — see e2e/README.md for the layout and the test-authoring pattern.

For interactive debugging (no test runner), run pnpm dev:host to boot the Vite host + asset server, prepare the silos protocol, and open a browser tab that lands directly in step 0 of the interview — no console paste.


What lives in this package, what doesn't

In:

  • the Redux store + every reducer / selector / thunk
  • all 17 stage interfaces and the navigation chrome
  • the dialog system, toast system, and stage error boundary
  • the synthetic network generator
  • the contract types and the schemas the host serialises against

Out:

  • everything that touches a database, a session cookie, or the network
  • protocol parsing and validation (use @codaco/protocol-validation)
  • export to GraphML / CSV (use @codaco/network-exporters)
  • network filtering / query DSL (use @codaco/network-query)

When in doubt: if it would still make sense to ship it embedded inside a non-Fresco host (Architect's preview mode, a CLI, an Electron app), it belongs here. If it talks to Fresco's Postgres or its auth layer, it doesn't.


License

MIT — see the repository root.

版本与发布记录

@codaco/interview 共发布 47 个版本,首次发布于 2026/05/05;过去 12 个月发布了 47 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
11.0.0latest2026/10/2119 天前
10.0.02026/10/1327 天前
9.1.02026/09/291 个月前
9.0.12026/09/022 个月前
9.0.02026/08/312 个月前
8.0.02026/08/112 个月前

其他发布通道:alpha → 1.0.0-alpha.17。

关于 @codaco/interview 的常见问题

@codaco/interview 是什么?

Network Canvas interview engine — Shell component, session contract.

如何安装 @codaco/interview?

在项目目录运行 npm install @codaco/interview,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:react(^19.3.0)、effect(^4.0.0)、motion(^13.4.6)、react-dom(^19.3.0)、tailwindcss(^4.3.3)、@codaco/fresco-ui(^8.1.0)、@codaco/shared-consts(^6.3.0)、@codaco/tailwind-config(^1.5.1)、@codaco/protocol-validation(^15.0.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@codaco/interview 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@codaco/interview 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@codaco/interview 可以商用吗?

@codaco/interview 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@codaco/interview 的下载量有多少?

根据 npm 官方下载统计,@codaco/interview 在 2026/10/10 – 2026/11/08 这 30 天内被下载 3,765 次,日均约 126 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@codaco/interview 还在维护吗?

最新版本 11.0.0 发布于 2026/10/21(19 天前);至今共发布 47 个版本,其中过去 12 个月发布了 47 个;首次发布于 2026/05/05。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。