@dataimago/interview 0.2.0-alpha.0 → 0.2.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/ProgressBar.tsx","../src/QuestionCard.tsx","../src/AnswerSidebar.tsx","../src/ReviewSummary.tsx"],"sourcesContent":["/**\n * @dataimago/interview — public surface barrel.\n *\n * Components (named exports — note the original apps/hub components\n * were default exports; the package exposes them as named exports for\n * tree-shaking and to make the import surface symmetric with @dataimago/ui):\n */\nexport { ProgressBar } from './ProgressBar';\nexport { QuestionCard } from './QuestionCard';\nexport { AnswerSidebar } from './AnswerSidebar';\nexport { ReviewSummary } from './ReviewSummary';\n\n/**\n * Schema contract — the public type surface that consumers populate with\n * their domain-specific question content.\n */\nexport type {\n QuestionInputType,\n InterviewQuestion,\n InterviewAnswers,\n} from './types';\n","'use client';\n\ninterface Props {\n current: number;\n total: number;\n estimatedMinutesRemaining: number;\n}\n\n/**\n * Linear progress indicator for the interview flow. Renders the\n * \"Question N of M\" caption, the estimated minutes remaining, and a\n * forest-700 fill bar at `(current / total) * 100%`.\n *\n * Iteration-1 visual identity (Phase B.2, 2026-05-06):\n * stone-100 surface with stone-200 border; forest-700 fill bar; stone-700\n * caption text. Pure-render component — no state-management dependency.\n *\n * Extracted from `apps/hub/src/components/interview/ProgressBar.tsx`\n * during iteration-3-B.1 (2026-05-07). No prop API change.\n */\nexport function ProgressBar({ current, total, estimatedMinutesRemaining }: Props) {\n const percent = Math.round((current / total) * 100);\n\n return (\n <div className=\"border-b border-stone-200 bg-stone-100 py-3 px-6\">\n <div className=\"mx-auto max-w-3xl\">\n <div className=\"flex items-center justify-between text-sm\">\n <span className=\"font-mono text-xs uppercase tracking-wider text-stone-700\">\n Question {current} of {total}\n </span>\n <span className=\"font-mono text-xs text-stone-500\">\n ~{estimatedMinutesRemaining} min remaining\n </span>\n </div>\n <div\n className=\"mt-2 h-1 w-full overflow-hidden rounded-full bg-stone-200\"\n role=\"progressbar\"\n aria-valuenow={percent}\n aria-valuemin={0}\n aria-valuemax={100}\n aria-label={`Interview progress: ${percent}% complete`}\n >\n <div\n className=\"h-full bg-forest-700 transition-all duration-300 ease-out\"\n style={{ width: `${percent}%` }}\n />\n </div>\n </div>\n </div>\n );\n}\n","'use client';\n\nimport { useState, useEffect } from 'react';\nimport type { InterviewQuestion } from './types';\n\ninterface Props {\n question: InterviewQuestion;\n /**\n * The currently-saved answer for this question (from the consumer's\n * state container). The component initializes its local form state\n * from this value and re-syncs when the question id changes.\n */\n existingAnswer?: string | string[];\n /**\n * Called when the user submits a non-empty answer (or any answer if\n * the question is `required: false`). The consumer is expected to\n * persist the answer into its state container and then advance via\n * `onNext`.\n */\n onAnswerSubmit: (questionId: string, value: string | string[]) => void;\n /** Called after `onAnswerSubmit` to advance to the next question. */\n onNext: () => void;\n /** Called when the user clicks the Back button. */\n onBack: () => void;\n isFirst: boolean;\n isLast: boolean;\n}\n\n/**\n * Single question renderer. Handles all input types: short-text, long-text,\n * select, multi-select, scale, list. Keyboard-navigable and screen-reader\n * safe.\n *\n * **Decoupled from any state-management library.** The original\n * apps/hub implementation imported `useInterviewStore` directly; this\n * extracted version receives the existing answer + change handler as\n * props, leaving the consumer free to use Zustand, Redux, useState,\n * server-side session state, or any other approach.\n *\n * Iteration-1 visual identity (Phase B.2, 2026-05-06):\n * Form input fields use stone-50 elevated surface + stone-200 default border\n * + forest-700 focus border (interactive-primary focus indicator). Selected\n * radio/checkbox option states use forest-700 border + forest-50 background.\n * The examples disclosure has its border-l-2 frame preserved as\n * border-copper-200 — same decorative-emphasis role as HeroSection's\n * etymology box and ReviewSummary's \"what happens next\" callout, per\n * iteration-1 §5.2's preserved-decorative-copper governance.\n *\n * Iteration-2 role-shift (2026-05-07): copper is no longer the brand-accent\n * moral-weight scale (the brand accent moved to sky-blue accent-500 #1f618d).\n * Copper survives as the \"warm decorative framing on the editorial scale\" —\n * a distinct decorative palette, not subordinate to the brand accent. The\n * border-copper-200 site below continues to serve its visual role (warm\n * framing for examples-list editorial content); its semantic relationship\n * to the brand accent is now zero. See wiki/decisions/iteration-2-blue-accent.md.\n *\n * Extracted from `apps/hub/src/components/interview/QuestionCard.tsx`\n * during iteration-3-B.1 (2026-05-07). Decoupled from `useInterviewStore`.\n */\nexport function QuestionCard({\n question,\n existingAnswer,\n onAnswerSubmit,\n onNext,\n onBack,\n isFirst,\n isLast,\n}: Props) {\n const [localValue, setLocalValue] = useState<string | string[]>(\n existingAnswer ?? (question.inputType === 'multi-select' || question.inputType === 'list' ? [] : '')\n );\n\n // Sync when question changes (consumer flipped to a different question;\n // local form state should reflect that question's existing answer).\n useEffect(() => {\n const current =\n existingAnswer ??\n (question.inputType === 'multi-select' || question.inputType === 'list' ? [] : '');\n setLocalValue(current);\n }, [question.id, existingAnswer, question.inputType]);\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n onAnswerSubmit(question.id, localValue);\n onNext();\n };\n\n const canAdvance = question.required\n ? Array.isArray(localValue)\n ? localValue.length > 0\n : localValue.trim().length > 0\n : true;\n\n return (\n <form onSubmit={handleSubmit} className=\"animate-slide-up\">\n {/* Prompt */}\n <h2 className=\"font-display text-3xl font-medium text-stone-900 sm:text-4xl\">\n {question.prompt}\n </h2>\n {question.subprompt && (\n <p className=\"mt-4 text-lg text-stone-700\">{question.subprompt}</p>\n )}\n\n {/* Input */}\n <div className=\"mt-8\">\n {question.inputType === 'short-text' && (\n <input\n type=\"text\"\n value={localValue as string}\n onChange={(e) => setLocalValue(e.target.value)}\n autoFocus\n className=\"w-full rounded-lg border-2 border-stone-200 bg-stone-50 px-4 py-3 text-lg text-stone-900 focus:border-forest-700 focus:outline-none\"\n placeholder=\"Type your answer…\"\n />\n )}\n\n {question.inputType === 'long-text' && (\n <textarea\n value={localValue as string}\n onChange={(e) => setLocalValue(e.target.value)}\n autoFocus\n rows={5}\n className=\"w-full rounded-lg border-2 border-stone-200 bg-stone-50 px-4 py-3 text-lg text-stone-900 focus:border-forest-700 focus:outline-none\"\n placeholder=\"Type your answer…\"\n />\n )}\n\n {question.inputType === 'select' && question.options && (\n <div className=\"space-y-3\" role=\"radiogroup\" aria-labelledby={`q-${question.id}`}>\n {question.options.map((opt) => (\n <label\n key={opt.value}\n className={`flex cursor-pointer items-start gap-3 rounded-lg border-2 p-4 transition-colors\n ${\n localValue === opt.value\n ? 'border-forest-700 bg-forest-50'\n : 'border-stone-200 bg-stone-50 hover:border-stone-400'\n }`}\n >\n <input\n type=\"radio\"\n name={question.id}\n value={opt.value}\n checked={localValue === opt.value}\n onChange={(e) => setLocalValue(e.target.value)}\n className=\"mt-1 h-4 w-4 text-forest-700\"\n />\n <div>\n <div className=\"font-medium text-stone-900\">{opt.label}</div>\n {opt.description && (\n <div className=\"mt-1 text-sm text-stone-700\">{opt.description}</div>\n )}\n </div>\n </label>\n ))}\n </div>\n )}\n\n {question.inputType === 'multi-select' && question.options && (\n <div className=\"space-y-2\" role=\"group\" aria-labelledby={`q-${question.id}`}>\n {question.options.map((opt) => {\n const values = (localValue as string[]) || [];\n const checked = values.includes(opt.value);\n return (\n <label\n key={opt.value}\n className={`flex cursor-pointer items-start gap-3 rounded-lg border-2 p-4 transition-colors\n ${\n checked\n ? 'border-forest-700 bg-forest-50'\n : 'border-stone-200 bg-stone-50 hover:border-stone-400'\n }`}\n >\n <input\n type=\"checkbox\"\n value={opt.value}\n checked={checked}\n onChange={(e) => {\n if (e.target.checked) {\n setLocalValue([...values, opt.value]);\n } else {\n setLocalValue(values.filter((v) => v !== opt.value));\n }\n }}\n className=\"mt-1 h-4 w-4 text-forest-700\"\n />\n <div>\n <div className=\"font-medium text-stone-900\">{opt.label}</div>\n {opt.description && (\n <div className=\"mt-1 text-sm text-stone-700\">{opt.description}</div>\n )}\n </div>\n </label>\n );\n })}\n </div>\n )}\n\n {question.inputType === 'list' && question.listRange && (\n <ListInput\n values={(localValue as string[]) || []}\n onChange={setLocalValue}\n min={question.listRange.min}\n max={question.listRange.max}\n />\n )}\n </div>\n\n {/* Examples */}\n {question.examples && question.examples.length > 0 && (\n <details className=\"mt-8\">\n <summary className=\"cursor-pointer text-sm font-medium text-forest-700 hover:text-forest-800\">\n Show examples from different fields\n </summary>\n {/* border-copper-200 PRESERVED: decorative-emphasis frame around\n illustrative content, same role as HeroSection's etymology box. */}\n <ul className=\"mt-4 space-y-3 border-l-2 border-copper-200 pl-5\">\n {question.examples.map((ex, i) => (\n <li key={i} className=\"text-sm italic text-stone-700\">\n “{ex}”\n </li>\n ))}\n </ul>\n </details>\n )}\n\n {/* Skip consequence (if applicable) */}\n {!question.required && question.skipConsequence && (\n <p className=\"mt-6 text-sm text-stone-500\">\n <span className=\"font-medium\">If you skip this:</span> {question.skipConsequence}\n </p>\n )}\n\n {/* Nav */}\n <div className=\"mt-10 flex items-center justify-between\">\n <button\n type=\"button\"\n onClick={onBack}\n disabled={isFirst}\n className=\"btn-ghost disabled:opacity-40\"\n >\n ← Back\n </button>\n <div className=\"flex gap-3\">\n {!question.required && (\n <button type=\"button\" onClick={onNext} className=\"btn-ghost\">\n Skip\n </button>\n )}\n <button type=\"submit\" disabled={!canAdvance} className=\"btn-primary\">\n {isLast ? 'Review answers' : 'Continue →'}\n </button>\n </div>\n </div>\n </form>\n );\n}\n\n/** List input (for glossary terms) — add/remove rows */\nfunction ListInput({\n values,\n onChange,\n min,\n max,\n}: {\n values: string[];\n onChange: (v: string[]) => void;\n min: number;\n max: number;\n}) {\n const items = values.length > 0 ? values : [''];\n\n const setItem = (i: number, v: string) => {\n const next = [...items];\n next[i] = v;\n onChange(next);\n };\n\n const addItem = () => {\n if (items.length < max) onChange([...items, '']);\n };\n\n const removeItem = (i: number) => {\n if (items.length <= 1) return;\n onChange(items.filter((_, idx) => idx !== i));\n };\n\n const validCount = items.filter((v) => v.trim().length > 0).length;\n\n return (\n <div>\n <ul className=\"space-y-2\">\n {items.map((item, i) => (\n <li key={i} className=\"flex items-center gap-2\">\n <span className=\"w-6 text-right font-mono text-xs text-stone-400\">{i + 1}.</span>\n <input\n type=\"text\"\n value={item}\n onChange={(e) => setItem(i, e.target.value)}\n className=\"flex-grow rounded-lg border-2 border-stone-200 bg-stone-50 px-3 py-2 text-stone-900 focus:border-forest-700 focus:outline-none\"\n placeholder={`Term ${i + 1}`}\n />\n <button\n type=\"button\"\n onClick={() => removeItem(i)}\n disabled={items.length <= 1}\n className=\"btn-ghost text-stone-500 disabled:opacity-30\"\n aria-label={`Remove term ${i + 1}`}\n >\n ×\n </button>\n </li>\n ))}\n </ul>\n <div className=\"mt-3 flex items-center justify-between\">\n <button\n type=\"button\"\n onClick={addItem}\n disabled={items.length >= max}\n className=\"text-sm font-medium text-forest-700 hover:text-forest-800 disabled:opacity-40\"\n >\n + Add term\n </button>\n <span className=\"font-mono text-xs text-stone-500\">\n {validCount} / {min}–{max}\n </span>\n </div>\n </div>\n );\n}\n","'use client';\n\nimport type { InterviewAnswers, InterviewQuestion } from './types';\n\ninterface Props {\n questions: InterviewQuestion[];\n currentIndex: number;\n /** Consumer's current answer record (read-only — engine doesn't write). */\n answers: InterviewAnswers;\n /** Called when the user clicks a sidebar item to jump to that question. */\n onSelectQuestion: (index: number) => void;\n}\n\n/**\n * Sidebar showing all answered questions. Clicking any returns to edit that\n * question without losing later answers. Implements the wiki's pattern:\n * \"Edit any prior answer — a sidebar shows all answered questions.\"\n *\n * **Decoupled from any state-management library.** The original\n * apps/hub implementation imported `useInterviewStore` directly; this\n * extracted version receives the answers + jump handler as props.\n *\n * Iteration-1 visual identity (Phase B.2, 2026-05-06):\n * Three item states with distinct token tiers:\n * - current → forest-700 border + forest-50 bg (interactive primary)\n * - answered → stone-200 border + stone-50 bg (default, hover stone-400)\n * - unanswered → stone-200/50 + stone-50/50 (muted via opacity preservation)\n *\n * Extracted from `apps/hub/src/components/interview/AnswerSidebar.tsx`\n * during iteration-3-B.1 (2026-05-07). Decoupled from `useInterviewStore`.\n */\nexport function AnswerSidebar({ questions, currentIndex, answers, onSelectQuestion }: Props) {\n return (\n <aside\n className=\"sticky top-24 hidden max-h-[calc(100vh-6rem)] overflow-y-auto lg:block\"\n aria-label=\"Completed answers\"\n >\n <h3 className=\"mb-3 font-mono text-xs uppercase tracking-wider text-stone-500\">\n Your answers\n </h3>\n <ol className=\"space-y-3\">\n {questions.map((q, i) => {\n const answer = answers[q.id];\n const answered =\n answer !== undefined &&\n answer !== '' &&\n !(Array.isArray(answer) && answer.length === 0);\n const isCurrent = i === currentIndex;\n\n return (\n <li key={q.id}>\n <button\n type=\"button\"\n onClick={() => onSelectQuestion(i)}\n className={`w-full rounded-lg border p-3 text-left transition-colors\n ${\n isCurrent\n ? 'border-forest-700 bg-forest-50'\n : answered\n ? 'border-stone-200 bg-stone-50 hover:border-stone-400'\n : 'border-stone-200/50 bg-stone-50/50 hover:border-stone-300'\n }`}\n >\n <div className=\"flex items-start gap-2\">\n <span\n className={`mt-0.5 font-mono text-xs\n ${\n isCurrent\n ? 'text-forest-700'\n : answered\n ? 'text-stone-500'\n : 'text-stone-400'\n }`}\n >\n {String(i + 1).padStart(2, '0')}.\n </span>\n <div className=\"flex-grow\">\n <div\n className={`text-xs font-medium\n ${answered ? 'text-stone-900' : 'text-stone-500'}`}\n >\n {q.prompt.length > 50 ? q.prompt.slice(0, 50) + '…' : q.prompt}\n </div>\n {answered && (\n <div className=\"mt-1 text-xs italic text-stone-500 line-clamp-1\">\n {Array.isArray(answer) ? answer.join(', ') : answer}\n </div>\n )}\n </div>\n </div>\n </button>\n </li>\n );\n })}\n </ol>\n </aside>\n );\n}\n","'use client';\n\nimport type { InterviewAnswers, InterviewQuestion } from './types';\n\ninterface Props {\n questions: InterviewQuestion[];\n /** Consumer's current answer record (read-only — engine doesn't write). */\n answers: InterviewAnswers;\n onGenerate: () => void;\n onEdit: (questionIndex: number) => void;\n generating: boolean;\n}\n\n/**\n * Final review screen — the penultimate step before generation.\n * Shows all answers so the user can confirm ownership of the output before\n * committing.\n *\n * **Decoupled from any state-management library.** The original\n * apps/hub implementation imported `useInterviewStore` directly; this\n * extracted version receives the answer record as a prop.\n *\n * Iteration-1 visual identity (Phase B.2, 2026-05-06):\n * Editorial Josefin display family on the heading and the \"What happens next\"\n * callout title. Stone-tier surfaces and content text throughout. The\n * \"What happens next\" callout box preserves border-copper-300 + bg-copper-50 —\n * iteration-1 §5.2's preserved-decorative-copper role applied to the\n * informational/forward-looking callout, same pattern as HeroSection's\n * etymology box.\n *\n * Iteration-2 role-shift (2026-05-07): copper is no longer subordinate to\n * the brand accent (which moved to sky-blue accent-500 #1f618d). Copper is\n * its own \"warm decorative framing on the editorial scale\" — distinct from\n * the brand-accent role. The \"What happens next\" callout continues to serve\n * its visual function (warm forward-looking framing); its semantic relation\n * to the brand accent is now zero. See wiki/decisions/iteration-2-blue-accent.md.\n *\n * Extracted from `apps/hub/src/components/interview/ReviewSummary.tsx`\n * during iteration-3-B.1 (2026-05-07). Decoupled from `useInterviewStore`.\n */\nexport function ReviewSummary({ questions, answers, onGenerate, onEdit, generating }: Props) {\n return (\n <div className=\"animate-slide-up\">\n <h2 className=\"font-display text-3xl font-medium text-stone-900 sm:text-4xl\">\n Review your answers\n </h2>\n <p className=\"mt-4 text-lg text-stone-700\">\n These become the seed content of your project. You can edit anything now, or continue and\n edit later through the platform.\n </p>\n\n <dl className=\"mt-10 space-y-6 border-t border-stone-200 pt-6\">\n {questions.map((q, i) => {\n const answer = answers[q.id];\n const display = Array.isArray(answer)\n ? answer.length > 0\n ? answer.join(', ')\n : '(not answered)'\n : (answer as string) || '(not answered)';\n const isEmpty =\n !answer ||\n (typeof answer === 'string' && answer.trim() === '') ||\n (Array.isArray(answer) && answer.length === 0);\n\n return (\n <div key={q.id} className=\"flex gap-4 border-b border-stone-200 pb-6\">\n <span className=\"font-mono text-sm text-stone-400 pt-1\">\n {String(i + 1).padStart(2, '0')}\n </span>\n <div className=\"flex-grow\">\n <dt className=\"text-sm font-medium text-stone-900\">{q.prompt}</dt>\n <dd className=\"mt-2 text-stone-900 whitespace-pre-wrap\">\n {isEmpty ? (\n <span className=\"italic text-stone-400\">Not answered</span>\n ) : (\n display\n )}\n </dd>\n </div>\n <button\n type=\"button\"\n onClick={() => onEdit(i)}\n className=\"btn-ghost text-sm self-start\"\n >\n Edit\n </button>\n </div>\n );\n })}\n </dl>\n\n {/* \"What happens next\" callout — copper PRESERVED as decorative\n informational frame, same role as HeroSection's etymology box.\n Per iteration-1 §5.2's preserved-decorative-copper governance. */}\n <div className=\"mt-10 rounded-lg border border-copper-300 bg-copper-50 p-6\">\n <h3 className=\"font-display text-xl text-stone-900\">What happens next</h3>\n <p className=\"mt-2 text-stone-900\">\n When you click Generate, the platform will create a GitHub repository in your account,\n populate it with your personalized CLAUDE.md, KNOWLEDGE.md, and wiki, and give you the\n URL. This takes about 30–90 seconds. You can edit everything afterward through the\n platform or through your repo.\n </p>\n <p className=\"mt-3 text-sm text-stone-700\">\n <span className=\"font-medium\">MVP note:</span> In this phase, GitHub repo creation is\n mocked for demonstration. Once OAuth credentials are configured, real repos will be\n created on your behalf.\n </p>\n </div>\n\n <div className=\"mt-8 flex justify-end\">\n <button\n type=\"button\"\n onClick={onGenerate}\n disabled={generating}\n className=\"btn-primary text-lg\"\n >\n {generating ? 'Generating your project…' : 'Generate my platform'}\n </button>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":["import_jsx_runtime","import_jsx_runtime","import_jsx_runtime"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/ProgressBar.tsx","../src/QuestionCard.tsx","../src/summarize.ts","../src/AnswerSidebar.tsx","../src/ReviewSummary.tsx"],"sourcesContent":["/**\n * @dataimago/interview — public surface barrel.\n *\n * Components (named exports — note the original apps/hub components\n * were default exports; the package exposes them as named exports for\n * tree-shaking and to make the import surface symmetric with @dataimago/ui):\n */\nexport { ProgressBar } from './ProgressBar';\nexport { QuestionCard } from './QuestionCard';\nexport { AnswerSidebar } from './AnswerSidebar';\nexport { ReviewSummary } from './ReviewSummary';\n\n/**\n * Schema contract — the public type surface that consumers populate with\n * their domain-specific question content.\n */\nexport type {\n QuestionInputType,\n InterviewQuestion,\n InterviewAnswers,\n AnswerValue,\n AnswerComposite,\n AnswerRepeater,\n AnswerFileUpload,\n FileEntry,\n} from './types';\n\n/**\n * Answer-rendering helpers — useful for consumers who want to display\n * answer previews outside the package's sidebar/review components.\n */\nexport { isAnswered, summarizeShort, summarizeLong } from './summarize';\n","'use client';\n\ninterface Props {\n current: number;\n total: number;\n estimatedMinutesRemaining: number;\n}\n\n/**\n * Linear progress indicator for the interview flow. Renders the\n * \"Question N of M\" caption, the estimated minutes remaining, and a\n * forest-700 fill bar at `(current / total) * 100%`.\n *\n * Iteration-1 visual identity (Phase B.2, 2026-05-06):\n * stone-100 surface with stone-200 border; forest-700 fill bar; stone-700\n * caption text. Pure-render component — no state-management dependency.\n *\n * Extracted from `apps/hub/src/components/interview/ProgressBar.tsx`\n * during iteration-3-B.1 (2026-05-07). No prop API change.\n */\nexport function ProgressBar({ current, total, estimatedMinutesRemaining }: Props) {\n const percent = Math.round((current / total) * 100);\n\n return (\n <div className=\"border-b border-stone-200 bg-stone-100 py-3 px-6\">\n <div className=\"mx-auto max-w-3xl\">\n <div className=\"flex items-center justify-between text-sm\">\n <span className=\"font-mono text-xs uppercase tracking-wider text-stone-700\">\n Question {current} of {total}\n </span>\n <span className=\"font-mono text-xs text-stone-500\">\n ~{estimatedMinutesRemaining} min remaining\n </span>\n </div>\n <div\n className=\"mt-2 h-1 w-full overflow-hidden rounded-full bg-stone-200\"\n role=\"progressbar\"\n aria-valuenow={percent}\n aria-valuemin={0}\n aria-valuemax={100}\n aria-label={`Interview progress: ${percent}% complete`}\n >\n <div\n className=\"h-full bg-forest-700 transition-all duration-300 ease-out\"\n style={{ width: `${percent}%` }}\n />\n </div>\n </div>\n </div>\n );\n}\n","'use client';\n\nimport { useState, useEffect } from 'react';\nimport type {\n InterviewQuestion,\n AnswerValue,\n AnswerComposite,\n AnswerRepeater,\n AnswerFileUpload,\n FileEntry,\n} from './types';\n\ninterface Props {\n question: InterviewQuestion;\n /**\n * The currently-saved answer for this question (from the consumer's\n * state container). The component initializes its local form state\n * from this value and re-syncs when the question id changes.\n */\n existingAnswer?: AnswerValue;\n /**\n * Called when the user submits an answer. The consumer is expected to\n * persist the answer into its state container and then advance via\n * `onNext`.\n */\n onAnswerSubmit: (questionId: string, value: AnswerValue) => void;\n /** Called after `onAnswerSubmit` to advance to the next question. */\n onNext: () => void;\n /** Called when the user clicks the Back button. */\n onBack: () => void;\n isFirst: boolean;\n isLast: boolean;\n}\n\n/**\n * Default value to use when no `existingAnswer` is supplied. Depends on\n * the question's `inputType`.\n */\nfunction defaultValueFor(question: InterviewQuestion): AnswerValue {\n switch (question.inputType) {\n case 'multi-select':\n case 'list':\n return [];\n case 'composite':\n return {} as AnswerComposite;\n case 'repeater':\n case 'file-upload':\n return [];\n default:\n return '';\n }\n}\n\n/**\n * Whether the user's current local value satisfies the required check\n * for the question.\n */\nfunction canAdvanceWith(question: InterviewQuestion, value: AnswerValue): boolean {\n if (!question.required) return true;\n if (value === undefined || value === null) return false;\n if (typeof value === 'string') return value.trim().length > 0;\n if (Array.isArray(value)) return value.length > 0;\n if (typeof value === 'object') return Object.keys(value).length > 0;\n return true;\n}\n\n/**\n * Single question renderer. Handles all input types: short-text, long-text,\n * single-select, multi-select, scale, list, date, composite, repeater,\n * file-upload. Keyboard-navigable and screen-reader safe.\n *\n * **Decoupled from any state-management library.** Consumers receive the\n * existing answer + change handler as props.\n *\n * Iteration-1 visual identity: form input fields use stone-50 elevated\n * surface + stone-200 default border + forest-700 focus border. Selected\n * radio/checkbox option states use forest-700 border + forest-50 background.\n * The examples disclosure has border-l-2 frame in border-copper-200 —\n * preserved-decorative-copper role per iteration-2-blue-accent.md.\n */\nexport function QuestionCard({\n question,\n existingAnswer,\n onAnswerSubmit,\n onNext,\n onBack,\n isFirst,\n isLast,\n}: Props) {\n const [localValue, setLocalValue] = useState<AnswerValue>(\n existingAnswer ?? defaultValueFor(question),\n );\n\n // Sync when question changes (consumer flipped to a different question;\n // local form state should reflect that question's existing answer).\n useEffect(() => {\n setLocalValue(existingAnswer ?? defaultValueFor(question));\n }, [question.id, existingAnswer, question.inputType]);\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n onAnswerSubmit(question.id, localValue);\n onNext();\n };\n\n const canAdvance = canAdvanceWith(question, localValue);\n\n return (\n <form onSubmit={handleSubmit} className=\"animate-slide-up\">\n {/* Prompt */}\n <h2 className=\"font-display text-3xl font-medium text-stone-900 sm:text-4xl\">\n {question.prompt}\n </h2>\n {question.subprompt && (\n <p className=\"mt-4 text-lg text-stone-700\">{question.subprompt}</p>\n )}\n\n {/* Input — dispatched per type */}\n <div className=\"mt-8\">\n <InputForType\n question={question}\n value={localValue}\n onChange={setLocalValue}\n />\n </div>\n\n {/* Examples */}\n {question.examples && question.examples.length > 0 && (\n <details className=\"mt-8\">\n <summary className=\"cursor-pointer text-sm font-medium text-forest-700 hover:text-forest-800\">\n Show examples from different fields\n </summary>\n {/* border-copper-200 PRESERVED: decorative-emphasis frame around\n illustrative content, same role as HeroSection's etymology box. */}\n <ul className=\"mt-4 space-y-3 border-l-2 border-copper-200 pl-5\">\n {question.examples.map((ex, i) => (\n <li key={i} className=\"text-sm italic text-stone-700\">\n “{ex}”\n </li>\n ))}\n </ul>\n </details>\n )}\n\n {/* Skip consequence (if applicable) */}\n {!question.required && question.skipConsequence && (\n <p className=\"mt-6 text-sm text-stone-500\">\n <span className=\"font-medium\">If you skip this:</span> {question.skipConsequence}\n </p>\n )}\n\n {/* Nav */}\n <div className=\"mt-10 flex items-center justify-between\">\n <button\n type=\"button\"\n onClick={onBack}\n disabled={isFirst}\n className=\"btn-ghost disabled:opacity-40\"\n >\n ← Back\n </button>\n <div className=\"flex gap-3\">\n {!question.required && (\n <button type=\"button\" onClick={onNext} className=\"btn-ghost\">\n Skip\n </button>\n )}\n <button type=\"submit\" disabled={!canAdvance} className=\"btn-primary\">\n {isLast ? 'Review answers' : 'Continue →'}\n </button>\n </div>\n </div>\n </form>\n );\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Per-input-type renderers\n// ─────────────────────────────────────────────────────────────────────────────\n\ninterface InputProps {\n question: InterviewQuestion;\n value: AnswerValue;\n onChange: (next: AnswerValue) => void;\n}\n\nfunction InputForType({ question, value, onChange }: InputProps) {\n switch (question.inputType) {\n case 'short-text':\n return <ShortTextInput value={value as string} onChange={onChange} />;\n case 'long-text':\n return <LongTextInput value={value as string} onChange={onChange} />;\n case 'single-select':\n return <SingleSelectInput question={question} value={value as string} onChange={onChange} />;\n case 'multi-select':\n return <MultiSelectInput question={question} value={(value as string[]) ?? []} onChange={onChange} />;\n case 'list':\n return <ListInput value={(value as string[]) ?? []} onChange={onChange} listRange={question.listRange} />;\n case 'scale':\n return <ScaleInput question={question} value={value as string} onChange={onChange} />;\n case 'date':\n return <DateInput value={value as string} onChange={onChange} />;\n case 'composite':\n return <CompositeInput question={question} value={(value as AnswerComposite) ?? {}} onChange={onChange} />;\n case 'repeater':\n return <RepeaterInput question={question} value={(value as AnswerRepeater) ?? []} onChange={onChange} />;\n case 'file-upload':\n return <FileUploadInput question={question} value={(value as AnswerFileUpload) ?? []} onChange={onChange} />;\n default: {\n const _exhaustive: never = question.inputType;\n return <UnknownTypeFallback inputType={_exhaustive} />;\n }\n }\n}\n\nfunction ShortTextInput({ value, onChange }: { value: string; onChange: (v: string) => void }) {\n return (\n <input\n type=\"text\"\n value={value ?? ''}\n onChange={(e) => onChange(e.target.value)}\n autoFocus\n className=\"w-full rounded-lg border-2 border-stone-200 bg-stone-50 px-4 py-3 text-lg text-stone-900 focus:border-forest-700 focus:outline-none\"\n placeholder=\"Type your answer…\"\n />\n );\n}\n\nfunction LongTextInput({ value, onChange }: { value: string; onChange: (v: string) => void }) {\n return (\n <textarea\n value={value ?? ''}\n onChange={(e) => onChange(e.target.value)}\n autoFocus\n rows={5}\n className=\"w-full rounded-lg border-2 border-stone-200 bg-stone-50 px-4 py-3 text-lg text-stone-900 focus:border-forest-700 focus:outline-none\"\n placeholder=\"Type your answer…\"\n />\n );\n}\n\nfunction SingleSelectInput({\n question,\n value,\n onChange,\n}: {\n question: InterviewQuestion;\n value: string;\n onChange: (v: string) => void;\n}) {\n return (\n <div className=\"space-y-3\" role=\"radiogroup\" aria-labelledby={`q-${question.id}`}>\n {(question.options ?? []).map((opt) => (\n <label\n key={opt.value}\n className={`flex cursor-pointer items-start gap-3 rounded-lg border-2 p-4 transition-colors ${\n value === opt.value\n ? 'border-forest-700 bg-forest-50'\n : 'border-stone-200 bg-stone-50 hover:border-stone-400'\n }`}\n >\n <input\n type=\"radio\"\n name={question.id}\n value={opt.value}\n checked={value === opt.value}\n onChange={(e) => onChange(e.target.value)}\n className=\"mt-1 h-4 w-4 text-forest-700\"\n />\n <div>\n <div className=\"font-medium text-stone-900\">{opt.label}</div>\n {opt.description && (\n <div className=\"mt-1 text-sm text-stone-700\">{opt.description}</div>\n )}\n </div>\n </label>\n ))}\n </div>\n );\n}\n\nfunction MultiSelectInput({\n question,\n value,\n onChange,\n}: {\n question: InterviewQuestion;\n value: string[];\n onChange: (v: string[]) => void;\n}) {\n return (\n <div className=\"space-y-2\" role=\"group\" aria-labelledby={`q-${question.id}`}>\n {(question.options ?? []).map((opt) => {\n const checked = value.includes(opt.value);\n return (\n <label\n key={opt.value}\n className={`flex cursor-pointer items-start gap-3 rounded-lg border-2 p-4 transition-colors ${\n checked\n ? 'border-forest-700 bg-forest-50'\n : 'border-stone-200 bg-stone-50 hover:border-stone-400'\n }`}\n >\n <input\n type=\"checkbox\"\n value={opt.value}\n checked={checked}\n onChange={(e) => {\n if (e.target.checked) onChange([...value, opt.value]);\n else onChange(value.filter((v) => v !== opt.value));\n }}\n className=\"mt-1 h-4 w-4 text-forest-700\"\n />\n <div>\n <div className=\"font-medium text-stone-900\">{opt.label}</div>\n {opt.description && (\n <div className=\"mt-1 text-sm text-stone-700\">{opt.description}</div>\n )}\n </div>\n </label>\n );\n })}\n </div>\n );\n}\n\nfunction ScaleInput({\n question,\n value,\n onChange,\n}: {\n question: InterviewQuestion;\n value: string;\n onChange: (v: string) => void;\n}) {\n const options = question.options ?? [];\n return (\n <div className=\"space-y-3\" role=\"radiogroup\" aria-labelledby={`q-${question.id}`}>\n {options.map((opt) => (\n <label\n key={opt.value}\n className={`flex cursor-pointer items-center gap-3 rounded-lg border-2 p-3 transition-colors ${\n value === opt.value\n ? 'border-forest-700 bg-forest-50'\n : 'border-stone-200 bg-stone-50 hover:border-stone-400'\n }`}\n >\n <input\n type=\"radio\"\n name={question.id}\n value={opt.value}\n checked={value === opt.value}\n onChange={(e) => onChange(e.target.value)}\n className=\"h-4 w-4 text-forest-700\"\n />\n <span className=\"font-mono text-sm text-stone-500\">{opt.value}</span>\n <span className=\"text-stone-900\">{opt.label}</span>\n </label>\n ))}\n </div>\n );\n}\n\nfunction DateInput({ value, onChange }: { value: string; onChange: (v: string) => void }) {\n return (\n <input\n type=\"date\"\n value={value ?? ''}\n onChange={(e) => onChange(e.target.value)}\n autoFocus\n className=\"rounded-lg border-2 border-stone-200 bg-stone-50 px-4 py-3 text-lg text-stone-900 focus:border-forest-700 focus:outline-none\"\n />\n );\n}\n\nfunction ListInput({\n value,\n onChange,\n listRange,\n}: {\n value: string[];\n onChange: (v: string[]) => void;\n listRange?: { min: number; max: number };\n}) {\n const items = value.length > 0 ? value : [''];\n const min = listRange?.min ?? 0;\n const max = listRange?.max ?? Infinity;\n\n const setItem = (i: number, v: string) => {\n const next = [...items];\n next[i] = v;\n onChange(next);\n };\n const addItem = () => {\n if (items.length < max) onChange([...items, '']);\n };\n const removeItem = (i: number) => {\n if (items.length <= 1) return;\n onChange(items.filter((_, idx) => idx !== i));\n };\n const validCount = items.filter((v) => v.trim().length > 0).length;\n\n return (\n <div>\n <ul className=\"space-y-2\">\n {items.map((item, i) => (\n <li key={i} className=\"flex items-center gap-2\">\n <span className=\"w-6 text-right font-mono text-xs text-stone-400\">{i + 1}.</span>\n <input\n type=\"text\"\n value={item}\n onChange={(e) => setItem(i, e.target.value)}\n className=\"flex-grow rounded-lg border-2 border-stone-200 bg-stone-50 px-3 py-2 text-stone-900 focus:border-forest-700 focus:outline-none\"\n placeholder={`Item ${i + 1}`}\n />\n <button\n type=\"button\"\n onClick={() => removeItem(i)}\n disabled={items.length <= 1}\n className=\"btn-ghost text-stone-500 disabled:opacity-30\"\n aria-label={`Remove item ${i + 1}`}\n >\n ×\n </button>\n </li>\n ))}\n </ul>\n <div className=\"mt-3 flex items-center justify-between\">\n <button\n type=\"button\"\n onClick={addItem}\n disabled={items.length >= max}\n className=\"text-sm font-medium text-forest-700 hover:text-forest-800 disabled:opacity-40\"\n >\n + Add item\n </button>\n {listRange && (\n <span className=\"font-mono text-xs text-stone-500\">\n {validCount} / {min}–{max === Infinity ? '∞' : max}\n </span>\n )}\n </div>\n </div>\n );\n}\n\nfunction CompositeInput({\n question,\n value,\n onChange,\n}: {\n question: InterviewQuestion;\n value: AnswerComposite;\n onChange: (v: AnswerComposite) => void;\n}) {\n const subQuestions = question.subQuestions ?? [];\n return (\n <div className=\"space-y-6 rounded-lg border-2 border-stone-200 bg-stone-50 p-4\">\n {subQuestions.map((sub) => (\n <div key={sub.id}>\n <div className=\"text-sm font-medium text-stone-900\">{sub.prompt}</div>\n {sub.subprompt && <div className=\"mt-1 text-xs text-stone-700\">{sub.subprompt}</div>}\n <div className=\"mt-2\">\n <InputForType\n question={sub}\n value={value[sub.id]}\n onChange={(next) => onChange({ ...value, [sub.id]: next })}\n />\n </div>\n </div>\n ))}\n </div>\n );\n}\n\nfunction RepeaterInput({\n question,\n value,\n onChange,\n}: {\n question: InterviewQuestion;\n value: AnswerRepeater;\n onChange: (v: AnswerRepeater) => void;\n}) {\n const rows = value.length > 0 ? value : [{} as Record<string, AnswerValue>];\n const itemSchema = question.itemSchema ?? [];\n const min = question.listRange?.min ?? 1;\n const max = question.listRange?.max ?? Infinity;\n\n const updateRow = (i: number, patch: Record<string, AnswerValue>) => {\n const next = [...rows];\n next[i] = { ...next[i], ...patch };\n onChange(next);\n };\n const addRow = () => {\n if (rows.length < max) onChange([...rows, {} as Record<string, AnswerValue>]);\n };\n const removeRow = (i: number) => {\n if (rows.length <= 1) return;\n onChange(rows.filter((_, idx) => idx !== i));\n };\n\n return (\n <div className=\"space-y-4\">\n {rows.map((row, i) => (\n <div key={i} className=\"rounded-lg border-2 border-stone-200 bg-stone-50 p-4\">\n <div className=\"mb-3 flex items-center justify-between\">\n <span className=\"font-mono text-xs uppercase tracking-wider text-stone-500\">\n Item {i + 1}\n </span>\n <button\n type=\"button\"\n onClick={() => removeRow(i)}\n disabled={rows.length <= min}\n className=\"btn-ghost text-xs text-stone-500 disabled:opacity-30\"\n aria-label={`Remove item ${i + 1}`}\n >\n Remove\n </button>\n </div>\n <div className=\"space-y-4\">\n {itemSchema.map((sub) => (\n <div key={sub.id}>\n <div className=\"text-sm font-medium text-stone-900\">{sub.prompt}</div>\n {sub.subprompt && (\n <div className=\"mt-1 text-xs text-stone-700\">{sub.subprompt}</div>\n )}\n <div className=\"mt-2\">\n <InputForType\n question={sub}\n value={row[sub.id]}\n onChange={(next) => updateRow(i, { [sub.id]: next })}\n />\n </div>\n </div>\n ))}\n </div>\n </div>\n ))}\n <button\n type=\"button\"\n onClick={addRow}\n disabled={rows.length >= max}\n className=\"text-sm font-medium text-forest-700 hover:text-forest-800 disabled:opacity-40\"\n >\n + Add another\n </button>\n </div>\n );\n}\n\nfunction FileUploadInput({\n question,\n value,\n onChange,\n}: {\n question: InterviewQuestion;\n value: AnswerFileUpload;\n onChange: (v: AnswerFileUpload) => void;\n}) {\n const itemSchema = question.itemSchema ?? [];\n\n const onFiles = (files: FileList | null) => {\n if (!files || files.length === 0) return;\n const entries: FileEntry[] = Array.from(files).map((f) => ({\n file: f,\n metadata: {},\n }));\n onChange([...value, ...entries]);\n };\n\n const updateMetadata = (i: number, patch: Record<string, AnswerValue>) => {\n const next = [...value];\n next[i] = { ...next[i], metadata: { ...next[i].metadata, ...patch } };\n onChange(next);\n };\n const removeEntry = (i: number) => onChange(value.filter((_, idx) => idx !== i));\n\n const onDrop = (e: React.DragEvent<HTMLDivElement>) => {\n e.preventDefault();\n onFiles(e.dataTransfer.files);\n };\n const onDragOver = (e: React.DragEvent<HTMLDivElement>) => e.preventDefault();\n\n return (\n <div className=\"space-y-4\">\n <div\n onDrop={onDrop}\n onDragOver={onDragOver}\n className=\"rounded-lg border-2 border-dashed border-stone-300 bg-stone-50 p-6 text-center\"\n >\n <p className=\"text-sm text-stone-700\">\n Drag files here, or{' '}\n <label className=\"cursor-pointer font-medium text-forest-700 hover:text-forest-800\">\n browse\n <input\n type=\"file\"\n multiple\n className=\"sr-only\"\n onChange={(e) => onFiles(e.target.files)}\n />\n </label>\n </p>\n <p className=\"mt-1 text-xs text-stone-500\">\n The package collects File objects + metadata; the consumer's pipeline handles upload.\n </p>\n </div>\n\n {value.length > 0 && (\n <ul className=\"space-y-4\">\n {value.map((entry, i) => {\n const name = entry.file.name;\n const size = 'size' in entry.file ? entry.file.size : undefined;\n return (\n <li key={i} className=\"rounded-lg border-2 border-stone-200 bg-stone-50 p-4\">\n <div className=\"flex items-center justify-between\">\n <div>\n <div className=\"font-medium text-stone-900\">{name}</div>\n {typeof size === 'number' && (\n <div className=\"font-mono text-xs text-stone-500\">\n {(size / 1024).toFixed(1)} KB\n </div>\n )}\n </div>\n <button\n type=\"button\"\n onClick={() => removeEntry(i)}\n className=\"btn-ghost text-xs text-stone-500\"\n aria-label={`Remove ${name}`}\n >\n Remove\n </button>\n </div>\n {itemSchema.length > 0 && (\n <div className=\"mt-4 space-y-3\">\n {itemSchema.map((sub) => (\n <div key={sub.id}>\n <div className=\"text-xs font-medium text-stone-900\">{sub.prompt}</div>\n <div className=\"mt-1\">\n <InputForType\n question={sub}\n value={entry.metadata[sub.id]}\n onChange={(next) => updateMetadata(i, { [sub.id]: next })}\n />\n </div>\n </div>\n ))}\n </div>\n )}\n </li>\n );\n })}\n </ul>\n )}\n </div>\n );\n}\n\nfunction UnknownTypeFallback({ inputType }: { inputType: never }) {\n // Render-time defensive guard for runtime values that bypass TS (e.g.,\n // YAML-loaded interview schemas with typos). Visible to dev; user sees a\n // muted \"unsupported\" notice.\n return (\n <div className=\"rounded-lg border-2 border-stone-300 bg-stone-50 p-4 text-sm text-stone-500\">\n Unsupported question input type:{' '}\n <code className=\"font-mono\">{String(inputType)}</code>\n </div>\n );\n}\n","/**\n * Helpers for rendering one-line / summary previews of answer values in\n * `AnswerSidebar` and `ReviewSummary`. Centralized so the two components\n * stay consistent when new answer shapes are added.\n */\nimport type { AnswerValue, FileEntry, InterviewQuestion } from './types';\n\nexport function isAnswered(value: AnswerValue): boolean {\n if (value === undefined || value === null) return false;\n if (typeof value === 'string') return value.trim().length > 0;\n if (Array.isArray(value)) return value.length > 0;\n if (typeof value === 'object') return Object.keys(value).length > 0;\n return true;\n}\n\n/**\n * A short, single-line preview suitable for sidebar items. Truncates\n * long content. Returns the empty string for unanswered values.\n */\nexport function summarizeShort(question: InterviewQuestion, value: AnswerValue, maxLen = 80): string {\n if (!isAnswered(value)) return '';\n switch (question.inputType) {\n case 'short-text':\n case 'long-text':\n case 'date':\n case 'scale':\n return truncate(String(value), maxLen);\n case 'single-select': {\n const v = String(value);\n const label = question.options?.find((o) => o.value === v)?.label;\n return truncate(label ?? v, maxLen);\n }\n case 'multi-select':\n case 'list': {\n const arr = value as string[];\n const labels = arr.map((v) => {\n const found = question.options?.find((o) => o.value === v)?.label;\n return found ?? v;\n });\n return truncate(labels.join(', '), maxLen);\n }\n case 'composite': {\n // Show the first sub-answer's preview if any.\n const obj = value as { [k: string]: AnswerValue };\n const subs = question.subQuestions ?? [];\n for (const sub of subs) {\n const subVal = obj[sub.id];\n if (isAnswered(subVal)) {\n return truncate(summarizeShort(sub, subVal, maxLen), maxLen);\n }\n }\n return '';\n }\n case 'repeater': {\n const rows = value as Array<{ [k: string]: AnswerValue }>;\n return `${rows.length} item${rows.length === 1 ? '' : 's'}`;\n }\n case 'file-upload': {\n const files = value as FileEntry[];\n return `${files.length} file${files.length === 1 ? '' : 's'}`;\n }\n default:\n return '';\n }\n}\n\n/**\n * A richer multi-line summary suitable for the ReviewSummary list. May\n * contain newlines; consumers should render in a `whitespace-pre-wrap`\n * container.\n */\nexport function summarizeLong(question: InterviewQuestion, value: AnswerValue): string {\n if (!isAnswered(value)) return '';\n switch (question.inputType) {\n case 'short-text':\n case 'long-text':\n case 'date':\n case 'scale':\n return String(value);\n case 'single-select': {\n const v = String(value);\n const label = question.options?.find((o) => o.value === v)?.label;\n return label ?? v;\n }\n case 'multi-select':\n case 'list': {\n const arr = value as string[];\n const labels = arr.map((v) => question.options?.find((o) => o.value === v)?.label ?? v);\n return labels.join('\\n• ').replace(/^/, '• ');\n }\n case 'composite': {\n const obj = value as { [k: string]: AnswerValue };\n const subs = question.subQuestions ?? [];\n const lines: string[] = [];\n for (const sub of subs) {\n const subVal = obj[sub.id];\n if (isAnswered(subVal)) {\n lines.push(`${sub.prompt}: ${summarizeShort(sub, subVal, 200)}`);\n }\n }\n return lines.join('\\n');\n }\n case 'repeater': {\n const rows = value as Array<{ [k: string]: AnswerValue }>;\n const itemSchema = question.itemSchema ?? [];\n const summary = rows.map((row, i) => {\n const parts: string[] = [`Item ${i + 1}:`];\n for (const sub of itemSchema) {\n const subVal = row[sub.id];\n if (isAnswered(subVal)) {\n parts.push(` ${sub.prompt}: ${summarizeShort(sub, subVal, 100)}`);\n }\n }\n return parts.join('\\n');\n });\n return summary.join('\\n');\n }\n case 'file-upload': {\n const files = value as FileEntry[];\n return files\n .map((f, i) => `${i + 1}. ${f.file.name}`)\n .join('\\n');\n }\n default:\n return '';\n }\n}\n\nfunction truncate(s: string, maxLen: number): string {\n return s.length > maxLen ? s.slice(0, maxLen - 1) + '…' : s;\n}\n","'use client';\n\nimport type { InterviewAnswers, InterviewQuestion } from './types';\nimport { isAnswered, summarizeShort } from './summarize';\n\ninterface Props {\n questions: InterviewQuestion[];\n currentIndex: number;\n /** Consumer's current answer record (read-only — engine doesn't write). */\n answers: InterviewAnswers;\n /** Called when the user clicks a sidebar item to jump to that question. */\n onSelectQuestion: (index: number) => void;\n}\n\n/**\n * Sidebar showing all answered questions. Clicking any returns to edit that\n * question without losing later answers. Implements the wiki's pattern:\n * \"Edit any prior answer — a sidebar shows all answered questions.\"\n *\n * **Decoupled from any state-management library.** Consumers receive the\n * answers + jump handler as props.\n *\n * Iteration-1 visual identity (Phase B.2, 2026-05-06):\n * Three item states with distinct token tiers:\n * - current → forest-700 border + forest-50 bg (interactive primary)\n * - answered → stone-200 border + stone-50 bg (default, hover stone-400)\n * - unanswered → stone-200/50 + stone-50/50 (muted via opacity preservation)\n *\n * D.2.1.b extension: previews for composite/repeater/file-upload answers\n * delegated to `summarize.ts` so all eight scalar + three compound types\n * render consistently.\n */\nexport function AnswerSidebar({ questions, currentIndex, answers, onSelectQuestion }: Props) {\n return (\n <aside\n className=\"sticky top-24 hidden max-h-[calc(100vh-6rem)] overflow-y-auto lg:block\"\n aria-label=\"Completed answers\"\n >\n <h3 className=\"mb-3 font-mono text-xs uppercase tracking-wider text-stone-500\">\n Your answers\n </h3>\n <ol className=\"space-y-3\">\n {questions.map((q, i) => {\n const answer = answers[q.id];\n const answered = isAnswered(answer);\n const isCurrent = i === currentIndex;\n const preview = answered ? summarizeShort(q, answer) : '';\n\n return (\n <li key={q.id}>\n <button\n type=\"button\"\n onClick={() => onSelectQuestion(i)}\n className={`w-full rounded-lg border p-3 text-left transition-colors ${\n isCurrent\n ? 'border-forest-700 bg-forest-50'\n : answered\n ? 'border-stone-200 bg-stone-50 hover:border-stone-400'\n : 'border-stone-200/50 bg-stone-50/50 hover:border-stone-300'\n }`}\n >\n <div className=\"flex items-start gap-2\">\n <span\n className={`mt-0.5 font-mono text-xs ${\n isCurrent\n ? 'text-forest-700'\n : answered\n ? 'text-stone-500'\n : 'text-stone-400'\n }`}\n >\n {String(i + 1).padStart(2, '0')}.\n </span>\n <div className=\"flex-grow\">\n <div\n className={`text-xs font-medium ${\n answered ? 'text-stone-900' : 'text-stone-500'\n }`}\n >\n {q.prompt.length > 50 ? q.prompt.slice(0, 50) + '…' : q.prompt}\n </div>\n {answered && (\n <div className=\"mt-1 text-xs italic text-stone-500 line-clamp-1\">\n {preview}\n </div>\n )}\n </div>\n </div>\n </button>\n </li>\n );\n })}\n </ol>\n </aside>\n );\n}\n","'use client';\n\nimport type { InterviewAnswers, InterviewQuestion } from './types';\nimport { isAnswered, summarizeLong } from './summarize';\n\ninterface Props {\n questions: InterviewQuestion[];\n /** Consumer's current answer record (read-only — engine doesn't write). */\n answers: InterviewAnswers;\n onGenerate: () => void;\n onEdit: (questionIndex: number) => void;\n generating: boolean;\n}\n\n/**\n * Final review screen — the penultimate step before generation.\n * Shows all answers so the user can confirm ownership of the output before\n * committing.\n *\n * **Decoupled from any state-management library.** Consumers receive the\n * answer record as a prop.\n *\n * Iteration-1 visual identity (Phase B.2, 2026-05-06):\n * Editorial Josefin display family on the heading and the \"What happens next\"\n * callout title. Stone-tier surfaces and content text throughout. The\n * \"What happens next\" callout box preserves border-copper-300 + bg-copper-50 —\n * preserved-decorative-copper role per iteration-2-blue-accent.md.\n *\n * D.2.1.b extension: rendering of composite/repeater/file-upload answers\n * delegated to `summarize.ts` so all answer types display consistently.\n */\nexport function ReviewSummary({ questions, answers, onGenerate, onEdit, generating }: Props) {\n return (\n <div className=\"animate-slide-up\">\n <h2 className=\"font-display text-3xl font-medium text-stone-900 sm:text-4xl\">\n Review your answers\n </h2>\n <p className=\"mt-4 text-lg text-stone-700\">\n These become the seed content of your project. You can edit anything now, or continue and\n edit later through the platform.\n </p>\n\n <dl className=\"mt-10 space-y-6 border-t border-stone-200 pt-6\">\n {questions.map((q, i) => {\n const answer = answers[q.id];\n const answered = isAnswered(answer);\n const display = answered ? summarizeLong(q, answer) : '';\n\n return (\n <div key={q.id} className=\"flex gap-4 border-b border-stone-200 pb-6\">\n <span className=\"font-mono text-sm text-stone-400 pt-1\">\n {String(i + 1).padStart(2, '0')}\n </span>\n <div className=\"flex-grow\">\n <dt className=\"text-sm font-medium text-stone-900\">{q.prompt}</dt>\n <dd className=\"mt-2 text-stone-900 whitespace-pre-wrap\">\n {answered ? (\n display\n ) : (\n <span className=\"italic text-stone-400\">Not answered</span>\n )}\n </dd>\n </div>\n <button\n type=\"button\"\n onClick={() => onEdit(i)}\n className=\"btn-ghost text-sm self-start\"\n >\n Edit\n </button>\n </div>\n );\n })}\n </dl>\n\n {/* \"What happens next\" callout — copper PRESERVED as decorative\n informational frame, same role as HeroSection's etymology box.\n Per iteration-1 §5.2's preserved-decorative-copper governance. */}\n <div className=\"mt-10 rounded-lg border border-copper-300 bg-copper-50 p-6\">\n <h3 className=\"font-display text-xl text-stone-900\">What happens next</h3>\n <p className=\"mt-2 text-stone-900\">\n When you click Generate, the platform will create a GitHub repository in your account,\n populate it with your personalized CLAUDE.md, KNOWLEDGE.md, and wiki, and give you the\n URL. This takes about 30–90 seconds. You can edit everything afterward through the\n platform or through your repo.\n </p>\n <p className=\"mt-3 text-sm text-stone-700\">\n <span className=\"font-medium\">MVP note:</span> In this phase, GitHub repo creation is\n mocked for demonstration. Once OAuth credentials are configured, real repos will be\n created on your behalf.\n </p>\n </div>\n\n <div className=\"mt-8 flex justify-end\">\n <button\n type=\"button\"\n onClick={onGenerate}\n disabled={generating}\n className=\"btn-primary text-lg\"\n >\n {generating ? 'Generating your project…' : 'Generate my platform'}\n </button>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":["import_jsx_runtime","import_jsx_runtime","import_jsx_runtime"]}
package/dist/index.d.cts CHANGED
@@ -23,38 +23,108 @@ declare function ProgressBar({ current, total, estimatedMinutesRemaining }: Prop
23
23
  * @dataimago/interview — schema contract.
24
24
  *
25
25
  * The types in this file are the package's public contract. Consumers
26
- * (apps/hub during the iteration-3-B.1 transition; dissertation-ai +
27
- * sgpc-ai + rpkg-ai once they consume the package) populate the actual
28
- * question content conforming to these shapes; the components in this
29
- * package render whatever the consumer provides.
26
+ * (Level-2 hubs like dissertation-ai, future sgpc-ai, future rpkg-ai)
27
+ * populate the actual question content conforming to these shapes; the
28
+ * components in this package render whatever the consumer provides.
30
29
  *
31
- * Extracted from `apps/hub/src/lib/interview-questions.ts` (the
32
- * `InterviewQuestion` + `QuestionInputType` definitions) and
33
- * `apps/hub/src/lib/interview-store.ts` (the `InterviewAnswers`
34
- * record) during iteration-3-B.1 (2026-05-07).
30
+ * Originally extracted from `apps/hub/src/lib/interview-questions.ts` +
31
+ * `apps/hub/src/lib/interview-store.ts` during iteration-3-B.1 (2026-05-07).
32
+ * Extended for the dissertation-ai use case during D.2.1.b (2026-06-02)
33
+ * see CHANGELOG for the new question types and the `select` → `single-select`
34
+ * rename.
35
35
  *
36
36
  * Design authority: `dataimago-design/wiki/patterns/onboarding-interview.md`.
37
37
  */
38
38
  /**
39
39
  * The set of input shapes the interview engine knows how to render.
40
40
  *
41
+ * Scalar-answer types (value is a `string` or `string[]`):
41
42
  * - `short-text` — single-line text field
42
43
  * - `long-text` — multi-line textarea
43
- * - `select` — single-choice radio group with `options[]`
44
+ * - `single-select` — single-choice radio group with `options[]`
45
+ * *(Renamed from `select` in v0.3; `select` is no longer recognized.)*
44
46
  * - `multi-select` — multi-choice checkbox group with `options[]`
45
- * - `scale` — numeric slider 1–N (consumer specifies range via `options[]`
46
- * labels; engine renders as a horizontal scale)
47
+ * - `scale` — numeric slider 1–N
47
48
  * - `list` — variable-length list of short-text items, bounded by `listRange`
49
+ * - `date` — single ISO 8601 date string (`<input type="date">`)
50
+ *
51
+ * Compound-answer types (value is a `Record` or `Record[]`):
52
+ * - `composite` — fixed set of sub-questions answered once. Value is
53
+ * `Record<subQuestionId, AnswerValue>`. Sub-questions live on
54
+ * `InterviewQuestion.subQuestions`.
55
+ * - `repeater` — variable-length list of records, each conforming to
56
+ * `itemSchema`. Value is `Array<Record<itemSchemaId, AnswerValue>>`. The
57
+ * user clicks "Add another" to add rows.
58
+ * - `file-upload` — like `repeater` but each row is anchored to an uploaded
59
+ * File. Value is `Array<FileEntry>`. The package's renderer collects
60
+ * `File` objects + per-file metadata; the actual upload-to-server
61
+ * pipeline is the consumer's responsibility (the package only collects
62
+ * answer state).
48
63
  */
49
- type QuestionInputType = 'short-text' | 'long-text' | 'select' | 'multi-select' | 'scale' | 'list';
64
+ type QuestionInputType = 'short-text' | 'long-text' | 'single-select' | 'multi-select' | 'scale' | 'list' | 'date' | 'composite' | 'repeater' | 'file-upload';
65
+ /**
66
+ * The runtime value carried by a single answer. The shape depends on the
67
+ * question's `inputType`:
68
+ *
69
+ * | inputType | Value shape |
70
+ * | ---------------- | -------------------------------------- |
71
+ * | short-text | `string` |
72
+ * | long-text | `string` |
73
+ * | single-select | `string` (the selected option's value) |
74
+ * | multi-select | `string[]` |
75
+ * | scale | `string` (numeric string) |
76
+ * | list | `string[]` |
77
+ * | date | `string` (ISO 8601 date) |
78
+ * | composite | `AnswerComposite` |
79
+ * | repeater | `AnswerRepeater` |
80
+ * | file-upload | `AnswerFileUpload` |
81
+ *
82
+ * `undefined` is used to mean "not yet answered."
83
+ */
84
+ type AnswerValue = string | string[] | AnswerComposite | AnswerRepeater | AnswerFileUpload | undefined;
85
+ /** Composite answer: a record keyed by sub-question id. */
86
+ type AnswerComposite = {
87
+ [subQuestionId: string]: AnswerValue;
88
+ };
89
+ /** Repeater answer: an array of records, each keyed by itemSchema sub-question id. */
90
+ type AnswerRepeater = Array<{
91
+ [itemSchemaId: string]: AnswerValue;
92
+ }>;
93
+ /**
94
+ * A single uploaded file entry for `file-upload` questions.
95
+ *
96
+ * - At interview time, `file` is a browser `File` object (from drag-and-drop
97
+ * or `<input type="file">`).
98
+ * - After the consumer uploads it (out-of-band), `file` may be replaced with
99
+ * a server-assigned descriptor (e.g., `{ name, path, size }`). The
100
+ * package treats anything matching the descriptor shape as "uploaded".
101
+ * - `metadata` carries per-file answers conforming to the question's
102
+ * `itemSchema` (e.g., title, author, description).
103
+ */
104
+ type FileEntry = {
105
+ file: File | {
106
+ name: string;
107
+ size: number;
108
+ path?: string;
109
+ type?: string;
110
+ };
111
+ metadata: {
112
+ [itemSchemaId: string]: AnswerValue;
113
+ };
114
+ };
115
+ type AnswerFileUpload = FileEntry[];
50
116
  /**
51
117
  * A single question in an interview flow. Consumers provide an array of
52
118
  * these to drive the engine.
53
119
  *
54
- * The `generates` field is consumer-specific metadata (e.g.,
55
- * "wiki/overview.md intro" or "CLAUDE.md project description") — the engine
56
- * doesn't interpret it; it's preserved through the answer collection pipeline
57
- * for the consumer's downstream generators (e.g., `@dataimago/onboarding-engine`).
120
+ * Field availability by `inputType`:
121
+ *
122
+ * | Field | Required for |
123
+ * | --------------- | --------------------------------------------- |
124
+ * | options | single-select, multi-select, scale |
125
+ * | listRange | list (optional bounds) |
126
+ * | subQuestions | composite (the inline sub-question fields) |
127
+ * | itemSchema | repeater + file-upload (per-row sub-questions)|
58
128
  */
59
129
  interface InterviewQuestion {
60
130
  /** Stable unique identifier; used as the answer-record key. */
@@ -69,7 +139,7 @@ interface InterviewQuestion {
69
139
  examples?: string[];
70
140
  /** Which input shape to render. */
71
141
  inputType: QuestionInputType;
72
- /** Required for select / multi-select / scale; ignored for other types. */
142
+ /** Required for single-select / multi-select / scale; ignored otherwise. */
73
143
  options?: Array<{
74
144
  value: string;
75
145
  label: string;
@@ -90,17 +160,34 @@ interface InterviewQuestion {
90
160
  min: number;
91
161
  max: number;
92
162
  };
163
+ /**
164
+ * Composite-only: the fixed set of sub-questions that get answered as part
165
+ * of this composite question. Their answers land at
166
+ * `answers[parentId][subQuestionId]`.
167
+ */
168
+ subQuestions?: InterviewQuestion[];
169
+ /**
170
+ * Repeater + file-upload: the per-row sub-question schema. Each row of the
171
+ * answer array is `Record<itemSchemaId, AnswerValue>`.
172
+ */
173
+ itemSchema?: InterviewQuestion[];
93
174
  }
94
175
  /**
95
- * The runtime answer record. Keys are `InterviewQuestion.id`; values are
96
- * either the answer string (for short-text, long-text, select, scale) or
97
- * an array of strings (for multi-select, list).
176
+ * The runtime answer record. Keys are `InterviewQuestion.id`; values
177
+ * conform to the per-`inputType` shape described in `AnswerValue` above.
98
178
  *
99
179
  * Consumers manage their own state container (Zustand, Redux, server-side
100
- * session, etc.) and pass the relevant slice into the components as props.
101
- * The engine is state-management-agnostic.
180
+ * session, useState, etc.) and pass the relevant slice into the components
181
+ * as props. The engine is state-management-agnostic.
182
+ *
183
+ * Consumers may narrow this to their domain-specific answer shape (e.g.,
184
+ * `dissertation-ai/apps/hub/src/lib/interview/answers.ts` defines a strict
185
+ * typed `InterviewAnswers`). This package's type is the loose lowest
186
+ * common denominator.
102
187
  */
103
- type InterviewAnswers = Record<string, string | string[]>;
188
+ type InterviewAnswers = {
189
+ [questionId: string]: AnswerValue;
190
+ };
104
191
 
105
192
  interface Props$2 {
106
193
  question: InterviewQuestion;
@@ -109,14 +196,13 @@ interface Props$2 {
109
196
  * state container). The component initializes its local form state
110
197
  * from this value and re-syncs when the question id changes.
111
198
  */
112
- existingAnswer?: string | string[];
199
+ existingAnswer?: AnswerValue;
113
200
  /**
114
- * Called when the user submits a non-empty answer (or any answer if
115
- * the question is `required: false`). The consumer is expected to
201
+ * Called when the user submits an answer. The consumer is expected to
116
202
  * persist the answer into its state container and then advance via
117
203
  * `onNext`.
118
204
  */
119
- onAnswerSubmit: (questionId: string, value: string | string[]) => void;
205
+ onAnswerSubmit: (questionId: string, value: AnswerValue) => void;
120
206
  /** Called after `onAnswerSubmit` to advance to the next question. */
121
207
  onNext: () => void;
122
208
  /** Called when the user clicks the Back button. */
@@ -126,34 +212,17 @@ interface Props$2 {
126
212
  }
127
213
  /**
128
214
  * Single question renderer. Handles all input types: short-text, long-text,
129
- * select, multi-select, scale, list. Keyboard-navigable and screen-reader
130
- * safe.
215
+ * single-select, multi-select, scale, list, date, composite, repeater,
216
+ * file-upload. Keyboard-navigable and screen-reader safe.
131
217
  *
132
- * **Decoupled from any state-management library.** The original
133
- * apps/hub implementation imported `useInterviewStore` directly; this
134
- * extracted version receives the existing answer + change handler as
135
- * props, leaving the consumer free to use Zustand, Redux, useState,
136
- * server-side session state, or any other approach.
218
+ * **Decoupled from any state-management library.** Consumers receive the
219
+ * existing answer + change handler as props.
137
220
  *
138
- * Iteration-1 visual identity (Phase B.2, 2026-05-06):
139
- * Form input fields use stone-50 elevated surface + stone-200 default border
140
- * + forest-700 focus border (interactive-primary focus indicator). Selected
221
+ * Iteration-1 visual identity: form input fields use stone-50 elevated
222
+ * surface + stone-200 default border + forest-700 focus border. Selected
141
223
  * radio/checkbox option states use forest-700 border + forest-50 background.
142
- * The examples disclosure has its border-l-2 frame preserved as
143
- * border-copper-200 — same decorative-emphasis role as HeroSection's
144
- * etymology box and ReviewSummary's "what happens next" callout, per
145
- * iteration-1 §5.2's preserved-decorative-copper governance.
146
- *
147
- * Iteration-2 role-shift (2026-05-07): copper is no longer the brand-accent
148
- * moral-weight scale (the brand accent moved to sky-blue accent-500 #1f618d).
149
- * Copper survives as the "warm decorative framing on the editorial scale" —
150
- * a distinct decorative palette, not subordinate to the brand accent. The
151
- * border-copper-200 site below continues to serve its visual role (warm
152
- * framing for examples-list editorial content); its semantic relationship
153
- * to the brand accent is now zero. See wiki/decisions/iteration-2-blue-accent.md.
154
- *
155
- * Extracted from `apps/hub/src/components/interview/QuestionCard.tsx`
156
- * during iteration-3-B.1 (2026-05-07). Decoupled from `useInterviewStore`.
224
+ * The examples disclosure has border-l-2 frame in border-copper-200 —
225
+ * preserved-decorative-copper role per iteration-2-blue-accent.md.
157
226
  */
158
227
  declare function QuestionCard({ question, existingAnswer, onAnswerSubmit, onNext, onBack, isFirst, isLast, }: Props$2): react_jsx_runtime.JSX.Element;
159
228
 
@@ -170,9 +239,8 @@ interface Props$1 {
170
239
  * question without losing later answers. Implements the wiki's pattern:
171
240
  * "Edit any prior answer — a sidebar shows all answered questions."
172
241
  *
173
- * **Decoupled from any state-management library.** The original
174
- * apps/hub implementation imported `useInterviewStore` directly; this
175
- * extracted version receives the answers + jump handler as props.
242
+ * **Decoupled from any state-management library.** Consumers receive the
243
+ * answers + jump handler as props.
176
244
  *
177
245
  * Iteration-1 visual identity (Phase B.2, 2026-05-06):
178
246
  * Three item states with distinct token tiers:
@@ -180,8 +248,9 @@ interface Props$1 {
180
248
  * - answered → stone-200 border + stone-50 bg (default, hover stone-400)
181
249
  * - unanswered → stone-200/50 + stone-50/50 (muted via opacity preservation)
182
250
  *
183
- * Extracted from `apps/hub/src/components/interview/AnswerSidebar.tsx`
184
- * during iteration-3-B.1 (2026-05-07). Decoupled from `useInterviewStore`.
251
+ * D.2.1.b extension: previews for composite/repeater/file-upload answers
252
+ * delegated to `summarize.ts` so all eight scalar + three compound types
253
+ * render consistently.
185
254
  */
186
255
  declare function AnswerSidebar({ questions, currentIndex, answers, onSelectQuestion }: Props$1): react_jsx_runtime.JSX.Element;
187
256
 
@@ -198,28 +267,37 @@ interface Props {
198
267
  * Shows all answers so the user can confirm ownership of the output before
199
268
  * committing.
200
269
  *
201
- * **Decoupled from any state-management library.** The original
202
- * apps/hub implementation imported `useInterviewStore` directly; this
203
- * extracted version receives the answer record as a prop.
270
+ * **Decoupled from any state-management library.** Consumers receive the
271
+ * answer record as a prop.
204
272
  *
205
273
  * Iteration-1 visual identity (Phase B.2, 2026-05-06):
206
274
  * Editorial Josefin display family on the heading and the "What happens next"
207
275
  * callout title. Stone-tier surfaces and content text throughout. The
208
276
  * "What happens next" callout box preserves border-copper-300 + bg-copper-50 —
209
- * iteration-1 §5.2's preserved-decorative-copper role applied to the
210
- * informational/forward-looking callout, same pattern as HeroSection's
211
- * etymology box.
212
- *
213
- * Iteration-2 role-shift (2026-05-07): copper is no longer subordinate to
214
- * the brand accent (which moved to sky-blue accent-500 #1f618d). Copper is
215
- * its own "warm decorative framing on the editorial scale" — distinct from
216
- * the brand-accent role. The "What happens next" callout continues to serve
217
- * its visual function (warm forward-looking framing); its semantic relation
218
- * to the brand accent is now zero. See wiki/decisions/iteration-2-blue-accent.md.
219
- *
220
- * Extracted from `apps/hub/src/components/interview/ReviewSummary.tsx`
221
- * during iteration-3-B.1 (2026-05-07). Decoupled from `useInterviewStore`.
277
+ * preserved-decorative-copper role per iteration-2-blue-accent.md.
278
+ *
279
+ * D.2.1.b extension: rendering of composite/repeater/file-upload answers
280
+ * delegated to `summarize.ts` so all answer types display consistently.
222
281
  */
223
282
  declare function ReviewSummary({ questions, answers, onGenerate, onEdit, generating }: Props): react_jsx_runtime.JSX.Element;
224
283
 
225
- export { AnswerSidebar, type InterviewAnswers, type InterviewQuestion, ProgressBar, QuestionCard, type QuestionInputType, ReviewSummary };
284
+ /**
285
+ * Helpers for rendering one-line / summary previews of answer values in
286
+ * `AnswerSidebar` and `ReviewSummary`. Centralized so the two components
287
+ * stay consistent when new answer shapes are added.
288
+ */
289
+
290
+ declare function isAnswered(value: AnswerValue): boolean;
291
+ /**
292
+ * A short, single-line preview suitable for sidebar items. Truncates
293
+ * long content. Returns the empty string for unanswered values.
294
+ */
295
+ declare function summarizeShort(question: InterviewQuestion, value: AnswerValue, maxLen?: number): string;
296
+ /**
297
+ * A richer multi-line summary suitable for the ReviewSummary list. May
298
+ * contain newlines; consumers should render in a `whitespace-pre-wrap`
299
+ * container.
300
+ */
301
+ declare function summarizeLong(question: InterviewQuestion, value: AnswerValue): string;
302
+
303
+ export { type AnswerComposite, type AnswerFileUpload, type AnswerRepeater, AnswerSidebar, type AnswerValue, type FileEntry, type InterviewAnswers, type InterviewQuestion, ProgressBar, QuestionCard, type QuestionInputType, ReviewSummary, isAnswered, summarizeLong, summarizeShort };