@dataimago/interview 0.2.0-alpha.5 → 0.2.0-alpha.6

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.
package/dist/index.cjs CHANGED
@@ -94,6 +94,30 @@ function canAdvanceWith(question, value) {
94
94
  if (typeof value === "object") return Object.keys(value).length > 0;
95
95
  return true;
96
96
  }
97
+ function validationError(question, value) {
98
+ const v = question.validation;
99
+ if (!v) return null;
100
+ if (typeof value !== "string" || value.trim().length === 0) return null;
101
+ try {
102
+ return new RegExp(v.pattern).test(value) ? null : v.message;
103
+ } catch {
104
+ return null;
105
+ }
106
+ }
107
+ function hasValidationError(question, value) {
108
+ if (validationError(question, value) !== null) return true;
109
+ if (question.inputType === "composite" && question.subQuestions) {
110
+ const rec = value ?? {};
111
+ return question.subQuestions.some((sub) => validationError(sub, rec[sub.id]) !== null);
112
+ }
113
+ if ((question.inputType === "repeater" || question.inputType === "file-upload") && question.itemSchema) {
114
+ const rows = value ?? [];
115
+ return rows.some(
116
+ (row) => question.itemSchema.some((sub) => validationError(sub, row[sub.id]) !== null)
117
+ );
118
+ }
119
+ return false;
120
+ }
97
121
  function QuestionCard({
98
122
  question,
99
123
  existingAnswer,
@@ -114,9 +138,12 @@ function QuestionCard({
114
138
  onAnswerSubmit(question.id, localValue);
115
139
  onNext();
116
140
  };
117
- const canAdvance = canAdvanceWith(question, localValue);
141
+ const canAdvance = canAdvanceWith(question, localValue) && !hasValidationError(question, localValue);
118
142
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("form", { onSubmit: handleSubmit, className: "animate-slide-up", children: [
119
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h2", { className: "font-display text-3xl font-medium text-stone-900 sm:text-4xl", children: question.prompt }),
143
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("h2", { className: "font-display text-3xl font-medium text-stone-900 sm:text-4xl", children: [
144
+ question.prompt,
145
+ !question.required && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "ml-3 inline-block translate-y-[-0.2em] rounded-full border border-stone-300 bg-stone-100 px-3 py-1 align-middle font-sans text-xs font-medium uppercase tracking-wider text-stone-700", children: "Optional" })
146
+ ] }),
120
147
  question.subprompt && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "mt-4 text-lg text-stone-700", children: question.subprompt }),
121
148
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mt-8", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
122
149
  InputForType,
@@ -160,7 +187,15 @@ function QuestionCard({
160
187
  function InputForType({ question, value, onChange }) {
161
188
  switch (question.inputType) {
162
189
  case "short-text":
163
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ShortTextInput, { value, onChange });
190
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
191
+ ShortTextInput,
192
+ {
193
+ question,
194
+ value,
195
+ onChange
196
+ },
197
+ question.id
198
+ );
164
199
  case "long-text":
165
200
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(LongTextInput, { value, onChange });
166
201
  case "single-select":
@@ -185,18 +220,33 @@ function InputForType({ question, value, onChange }) {
185
220
  }
186
221
  }
187
222
  }
188
- function ShortTextInput({ value, onChange }) {
189
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
190
- "input",
191
- {
192
- type: "text",
193
- value: value ?? "",
194
- onChange: (e) => onChange(e.target.value),
195
- autoFocus: true,
196
- 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",
197
- placeholder: "Type your answer\u2026"
198
- }
199
- );
223
+ function ShortTextInput({
224
+ question,
225
+ value,
226
+ onChange
227
+ }) {
228
+ const [blurred, setBlurred] = (0, import_react.useState)(false);
229
+ const errorId = (0, import_react.useId)();
230
+ const error = question ? validationError(question, value) : null;
231
+ const showError = blurred && error !== null;
232
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
233
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
234
+ "input",
235
+ {
236
+ type: "text",
237
+ value: value ?? "",
238
+ onChange: (e) => onChange(e.target.value),
239
+ onBlur: () => setBlurred(true),
240
+ autoFocus: true,
241
+ "aria-label": question?.prompt,
242
+ "aria-invalid": showError || void 0,
243
+ "aria-describedby": showError ? errorId : void 0,
244
+ className: `w-full rounded-lg border-2 bg-stone-50 px-4 py-3 text-lg text-stone-900 focus:outline-none ${showError ? "border-red-400 focus:border-red-700" : "border-stone-200 focus:border-forest-700"}`,
245
+ placeholder: "Type your answer\u2026"
246
+ }
247
+ ),
248
+ showError && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { id: errorId, className: "mt-2 text-sm font-medium text-red-800", children: error })
249
+ ] });
200
250
  }
201
251
  function LongTextInput({ value, onChange }) {
202
252
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
@@ -726,7 +776,12 @@ function AnswerSidebar({ questions, currentIndex, answers, onSelectQuestion }) {
726
776
  children: q.prompt.length > 50 ? q.prompt.slice(0, 50) + "\u2026" : q.prompt
727
777
  }
728
778
  ),
729
- answered && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mt-1 text-xs italic text-stone-700 line-clamp-1", children: preview })
779
+ answered && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mt-1 text-xs italic text-stone-700 line-clamp-1", children: preview }),
780
+ !answered && q.required && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mt-1 text-xs font-medium text-amber-800", children: [
781
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { "aria-hidden": "true", children: "\u25CF" }),
782
+ " Required \u2014 not yet answered"
783
+ ] }),
784
+ !answered && !q.required && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "mt-1 text-xs text-stone-600", children: "Optional" })
730
785
  ] })
731
786
  ] })
732
787
  }
@@ -1 +1 @@
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-700\">\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-700\">\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} ariaLabel={question.prompt} />;\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-700\">{opt.value}</span>\n <span className=\"text-stone-900\">{opt.label}</span>\n </label>\n ))}\n </div>\n );\n}\n\nfunction DateInput({\n value,\n onChange,\n ariaLabel,\n}: {\n value: string;\n onChange: (v: string) => void;\n ariaLabel: string;\n}) {\n return (\n <input\n type=\"date\"\n aria-label={ariaLabel}\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-600\">{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-700 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=\"inline-flex min-h-12 items-center px-2 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-700\">\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-700\">\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-700 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=\"inline-flex min-h-12 items-center px-2 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 PDFs or other 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-700\">\n You'll add a title, author, and description for each file below.\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-700\">\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-700\"\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-700\">\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-700\">\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-700'\n : 'text-stone-600'\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-700'\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-700 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 // A <div> child of <dl> may contain ONLY dt/dd groups (HTML spec;\n // axe definition-list), so the index + Edit button live INSIDE\n // <dt>. Subgrid keeps all three columns aligned across dt and dd\n // without extra wrappers.\n <div\n key={q.id}\n className=\"grid grid-cols-[auto_1fr_auto] gap-x-4 border-b border-stone-200 pb-6\"\n >\n <dt className=\"col-span-3 grid grid-cols-subgrid\">\n <span aria-hidden=\"true\" className=\"pt-1 font-mono text-sm text-stone-600\">\n {String(i + 1).padStart(2, '0')}\n </span>\n <span className=\"text-sm font-medium text-stone-900\">{q.prompt}</span>\n <button\n type=\"button\"\n onClick={() => onEdit(i)}\n aria-label={`Edit: ${q.prompt}`}\n className=\"btn-ghost self-start text-sm\"\n >\n Edit\n </button>\n </dt>\n <dd className=\"col-span-3 mt-2 grid grid-cols-subgrid\">\n <span className=\"col-start-2 whitespace-pre-wrap text-stone-900\">\n {answered ? (\n display\n ) : (\n <span className=\"italic text-stone-600\">Not answered</span>\n )}\n </span>\n </dd>\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-700\">\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, useId } 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 * On-blur format validation (D.3 It-10). Returns the consumer's\n * author-language message when a non-empty string value fails the\n * consumer-supplied pattern; empty values are `required`'s concern. Fails\n * open on an invalid consumer regex (a content bug must not dead-end the\n * author).\n */\nfunction validationError(question: InterviewQuestion, value: AnswerValue): string | null {\n const v = question.validation;\n if (!v) return null;\n if (typeof value !== 'string' || value.trim().length === 0) return null;\n try {\n return new RegExp(v.pattern).test(value) ? null : v.message;\n } catch {\n return null;\n }\n}\n\n/**\n * Whether any validation error exists anywhere in the question's value —\n * top-level, composite sub-questions, or repeater/file-upload rows. Gates\n * Continue the same way required-empty does.\n */\nfunction hasValidationError(question: InterviewQuestion, value: AnswerValue): boolean {\n if (validationError(question, value) !== null) return true;\n if (question.inputType === 'composite' && question.subQuestions) {\n const rec = (value ?? {}) as AnswerComposite;\n return question.subQuestions.some((sub) => validationError(sub, rec[sub.id]) !== null);\n }\n if (\n (question.inputType === 'repeater' || question.inputType === 'file-upload') &&\n question.itemSchema\n ) {\n const rows = (value ?? []) as AnswerRepeater;\n return rows.some((row) =>\n question.itemSchema!.some((sub) => validationError(sub, row[sub.id] as AnswerValue) !== null),\n );\n }\n return false;\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 =\n canAdvanceWith(question, localValue) && !hasValidationError(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 {!question.required && (\n <span className=\"ml-3 inline-block translate-y-[-0.2em] rounded-full border border-stone-300 bg-stone-100 px-3 py-1 align-middle font-sans text-xs font-medium uppercase tracking-wider text-stone-700\">\n Optional\n </span>\n )}\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-700\">\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 (\n <ShortTextInput\n key={question.id}\n question={question}\n value={value as string}\n onChange={onChange}\n />\n );\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} ariaLabel={question.prompt} />;\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({\n question,\n value,\n onChange,\n}: {\n question?: InterviewQuestion;\n value: string;\n onChange: (v: string) => void;\n}) {\n // Errors surface on blur (not while typing), then clear live once the\n // value passes — validate-in-place per the skillful-coping rubric.\n const [blurred, setBlurred] = useState(false);\n const errorId = useId();\n const error = question ? validationError(question, value) : null;\n const showError = blurred && error !== null;\n return (\n <>\n <input\n type=\"text\"\n value={value ?? ''}\n onChange={(e) => onChange(e.target.value)}\n onBlur={() => setBlurred(true)}\n autoFocus\n aria-label={question?.prompt}\n aria-invalid={showError || undefined}\n aria-describedby={showError ? errorId : undefined}\n className={`w-full rounded-lg border-2 bg-stone-50 px-4 py-3 text-lg text-stone-900 focus:outline-none ${\n showError\n ? 'border-red-400 focus:border-red-700'\n : 'border-stone-200 focus:border-forest-700'\n }`}\n placeholder=\"Type your answer…\"\n />\n {showError && (\n <p id={errorId} className=\"mt-2 text-sm font-medium text-red-800\">\n {error}\n </p>\n )}\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-700\">{opt.value}</span>\n <span className=\"text-stone-900\">{opt.label}</span>\n </label>\n ))}\n </div>\n );\n}\n\nfunction DateInput({\n value,\n onChange,\n ariaLabel,\n}: {\n value: string;\n onChange: (v: string) => void;\n ariaLabel: string;\n}) {\n return (\n <input\n type=\"date\"\n aria-label={ariaLabel}\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-600\">{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-700 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=\"inline-flex min-h-12 items-center px-2 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-700\">\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-700\">\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-700 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=\"inline-flex min-h-12 items-center px-2 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 PDFs or other 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-700\">\n You'll add a title, author, and description for each file below.\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-700\">\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-700\"\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-700\">\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-700\">\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-700'\n : 'text-stone-600'\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-700'\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-700 line-clamp-1\">\n {preview}\n </div>\n )}\n {/* D.3 It-10: unanswered REQUIRED questions are the\n blockers to Generate — mark them so the author can\n tell at a glance which gaps matter. */}\n {!answered && q.required && (\n <div className=\"mt-1 text-xs font-medium text-amber-800\">\n <span aria-hidden=\"true\">●</span> Required — not yet answered\n </div>\n )}\n {!answered && !q.required && (\n <div className=\"mt-1 text-xs text-stone-600\">Optional</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 // A <div> child of <dl> may contain ONLY dt/dd groups (HTML spec;\n // axe definition-list), so the index + Edit button live INSIDE\n // <dt>. Subgrid keeps all three columns aligned across dt and dd\n // without extra wrappers.\n <div\n key={q.id}\n className=\"grid grid-cols-[auto_1fr_auto] gap-x-4 border-b border-stone-200 pb-6\"\n >\n <dt className=\"col-span-3 grid grid-cols-subgrid\">\n <span aria-hidden=\"true\" className=\"pt-1 font-mono text-sm text-stone-600\">\n {String(i + 1).padStart(2, '0')}\n </span>\n <span className=\"text-sm font-medium text-stone-900\">{q.prompt}</span>\n <button\n type=\"button\"\n onClick={() => onEdit(i)}\n aria-label={`Edit: ${q.prompt}`}\n className=\"btn-ghost self-start text-sm\"\n >\n Edit\n </button>\n </dt>\n <dd className=\"col-span-3 mt-2 grid grid-cols-subgrid\">\n <span className=\"col-start-2 whitespace-pre-wrap text-stone-900\">\n {answered ? (\n display\n ) : (\n <span className=\"italic text-stone-600\">Not answered</span>\n )}\n </span>\n </dd>\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
@@ -147,6 +147,24 @@ interface InterviewQuestion {
147
147
  }>;
148
148
  /** Whether the consumer can advance without an answer. */
149
149
  required: boolean;
150
+ /**
151
+ * Optional on-blur format check for text answers (D.3 It-10). The engine
152
+ * runs the consumer-supplied regex against non-empty string values and
153
+ * shows the consumer's author-language `message` inline when it fails;
154
+ * Continue stays disabled until the value passes (the same affordance as
155
+ * required-empty). Applies to `short-text` questions wherever they appear
156
+ * — top-level, composite `subQuestions`, and repeater/file-upload
157
+ * `itemSchema` rows. Empty values are the `required` flag's concern, not
158
+ * validation's. The engine stays domain-agnostic: patterns + messages are
159
+ * consumer content (framework-configurability) — e.g. dissertation-ai
160
+ * supplies email / GitHub-username / repo-name rules.
161
+ */
162
+ validation?: {
163
+ /** Regex source (compiled with `new RegExp(pattern)`). */
164
+ pattern: string;
165
+ /** Author-language error shown inline when the pattern fails. */
166
+ message: string;
167
+ };
150
168
  /** Optional explanation shown when the user attempts to skip. */
151
169
  skipConsequence?: string;
152
170
  /**
package/dist/index.d.ts CHANGED
@@ -147,6 +147,24 @@ interface InterviewQuestion {
147
147
  }>;
148
148
  /** Whether the consumer can advance without an answer. */
149
149
  required: boolean;
150
+ /**
151
+ * Optional on-blur format check for text answers (D.3 It-10). The engine
152
+ * runs the consumer-supplied regex against non-empty string values and
153
+ * shows the consumer's author-language `message` inline when it fails;
154
+ * Continue stays disabled until the value passes (the same affordance as
155
+ * required-empty). Applies to `short-text` questions wherever they appear
156
+ * — top-level, composite `subQuestions`, and repeater/file-upload
157
+ * `itemSchema` rows. Empty values are the `required` flag's concern, not
158
+ * validation's. The engine stays domain-agnostic: patterns + messages are
159
+ * consumer content (framework-configurability) — e.g. dissertation-ai
160
+ * supplies email / GitHub-username / repo-name rules.
161
+ */
162
+ validation?: {
163
+ /** Regex source (compiled with `new RegExp(pattern)`). */
164
+ pattern: string;
165
+ /** Author-language error shown inline when the pattern fails. */
166
+ message: string;
167
+ };
150
168
  /** Optional explanation shown when the user attempts to skip. */
151
169
  skipConsequence?: string;
152
170
  /**
package/dist/index.js CHANGED
@@ -38,8 +38,8 @@ function ProgressBar({ current, total, estimatedMinutesRemaining }) {
38
38
  }
39
39
 
40
40
  // src/QuestionCard.tsx
41
- import { useState, useEffect } from "react";
42
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
41
+ import { useState, useEffect, useId } from "react";
42
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
43
43
  function defaultValueFor(question) {
44
44
  switch (question.inputType) {
45
45
  case "multi-select":
@@ -62,6 +62,30 @@ function canAdvanceWith(question, value) {
62
62
  if (typeof value === "object") return Object.keys(value).length > 0;
63
63
  return true;
64
64
  }
65
+ function validationError(question, value) {
66
+ const v = question.validation;
67
+ if (!v) return null;
68
+ if (typeof value !== "string" || value.trim().length === 0) return null;
69
+ try {
70
+ return new RegExp(v.pattern).test(value) ? null : v.message;
71
+ } catch {
72
+ return null;
73
+ }
74
+ }
75
+ function hasValidationError(question, value) {
76
+ if (validationError(question, value) !== null) return true;
77
+ if (question.inputType === "composite" && question.subQuestions) {
78
+ const rec = value ?? {};
79
+ return question.subQuestions.some((sub) => validationError(sub, rec[sub.id]) !== null);
80
+ }
81
+ if ((question.inputType === "repeater" || question.inputType === "file-upload") && question.itemSchema) {
82
+ const rows = value ?? [];
83
+ return rows.some(
84
+ (row) => question.itemSchema.some((sub) => validationError(sub, row[sub.id]) !== null)
85
+ );
86
+ }
87
+ return false;
88
+ }
65
89
  function QuestionCard({
66
90
  question,
67
91
  existingAnswer,
@@ -82,9 +106,12 @@ function QuestionCard({
82
106
  onAnswerSubmit(question.id, localValue);
83
107
  onNext();
84
108
  };
85
- const canAdvance = canAdvanceWith(question, localValue);
109
+ const canAdvance = canAdvanceWith(question, localValue) && !hasValidationError(question, localValue);
86
110
  return /* @__PURE__ */ jsxs2("form", { onSubmit: handleSubmit, className: "animate-slide-up", children: [
87
- /* @__PURE__ */ jsx2("h2", { className: "font-display text-3xl font-medium text-stone-900 sm:text-4xl", children: question.prompt }),
111
+ /* @__PURE__ */ jsxs2("h2", { className: "font-display text-3xl font-medium text-stone-900 sm:text-4xl", children: [
112
+ question.prompt,
113
+ !question.required && /* @__PURE__ */ jsx2("span", { className: "ml-3 inline-block translate-y-[-0.2em] rounded-full border border-stone-300 bg-stone-100 px-3 py-1 align-middle font-sans text-xs font-medium uppercase tracking-wider text-stone-700", children: "Optional" })
114
+ ] }),
88
115
  question.subprompt && /* @__PURE__ */ jsx2("p", { className: "mt-4 text-lg text-stone-700", children: question.subprompt }),
89
116
  /* @__PURE__ */ jsx2("div", { className: "mt-8", children: /* @__PURE__ */ jsx2(
90
117
  InputForType,
@@ -128,7 +155,15 @@ function QuestionCard({
128
155
  function InputForType({ question, value, onChange }) {
129
156
  switch (question.inputType) {
130
157
  case "short-text":
131
- return /* @__PURE__ */ jsx2(ShortTextInput, { value, onChange });
158
+ return /* @__PURE__ */ jsx2(
159
+ ShortTextInput,
160
+ {
161
+ question,
162
+ value,
163
+ onChange
164
+ },
165
+ question.id
166
+ );
132
167
  case "long-text":
133
168
  return /* @__PURE__ */ jsx2(LongTextInput, { value, onChange });
134
169
  case "single-select":
@@ -153,18 +188,33 @@ function InputForType({ question, value, onChange }) {
153
188
  }
154
189
  }
155
190
  }
156
- function ShortTextInput({ value, onChange }) {
157
- return /* @__PURE__ */ jsx2(
158
- "input",
159
- {
160
- type: "text",
161
- value: value ?? "",
162
- onChange: (e) => onChange(e.target.value),
163
- autoFocus: true,
164
- 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",
165
- placeholder: "Type your answer\u2026"
166
- }
167
- );
191
+ function ShortTextInput({
192
+ question,
193
+ value,
194
+ onChange
195
+ }) {
196
+ const [blurred, setBlurred] = useState(false);
197
+ const errorId = useId();
198
+ const error = question ? validationError(question, value) : null;
199
+ const showError = blurred && error !== null;
200
+ return /* @__PURE__ */ jsxs2(Fragment, { children: [
201
+ /* @__PURE__ */ jsx2(
202
+ "input",
203
+ {
204
+ type: "text",
205
+ value: value ?? "",
206
+ onChange: (e) => onChange(e.target.value),
207
+ onBlur: () => setBlurred(true),
208
+ autoFocus: true,
209
+ "aria-label": question?.prompt,
210
+ "aria-invalid": showError || void 0,
211
+ "aria-describedby": showError ? errorId : void 0,
212
+ className: `w-full rounded-lg border-2 bg-stone-50 px-4 py-3 text-lg text-stone-900 focus:outline-none ${showError ? "border-red-400 focus:border-red-700" : "border-stone-200 focus:border-forest-700"}`,
213
+ placeholder: "Type your answer\u2026"
214
+ }
215
+ ),
216
+ showError && /* @__PURE__ */ jsx2("p", { id: errorId, className: "mt-2 text-sm font-medium text-red-800", children: error })
217
+ ] });
168
218
  }
169
219
  function LongTextInput({ value, onChange }) {
170
220
  return /* @__PURE__ */ jsx2(
@@ -694,7 +744,12 @@ function AnswerSidebar({ questions, currentIndex, answers, onSelectQuestion }) {
694
744
  children: q.prompt.length > 50 ? q.prompt.slice(0, 50) + "\u2026" : q.prompt
695
745
  }
696
746
  ),
697
- answered && /* @__PURE__ */ jsx3("div", { className: "mt-1 text-xs italic text-stone-700 line-clamp-1", children: preview })
747
+ answered && /* @__PURE__ */ jsx3("div", { className: "mt-1 text-xs italic text-stone-700 line-clamp-1", children: preview }),
748
+ !answered && q.required && /* @__PURE__ */ jsxs3("div", { className: "mt-1 text-xs font-medium text-amber-800", children: [
749
+ /* @__PURE__ */ jsx3("span", { "aria-hidden": "true", children: "\u25CF" }),
750
+ " Required \u2014 not yet answered"
751
+ ] }),
752
+ !answered && !q.required && /* @__PURE__ */ jsx3("div", { className: "mt-1 text-xs text-stone-600", children: "Optional" })
698
753
  ] })
699
754
  ] })
700
755
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/ProgressBar.tsx","../src/QuestionCard.tsx","../src/summarize.ts","../src/AnswerSidebar.tsx","../src/ReviewSummary.tsx"],"sourcesContent":["'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-700\">\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-700\">\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} ariaLabel={question.prompt} />;\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-700\">{opt.value}</span>\n <span className=\"text-stone-900\">{opt.label}</span>\n </label>\n ))}\n </div>\n );\n}\n\nfunction DateInput({\n value,\n onChange,\n ariaLabel,\n}: {\n value: string;\n onChange: (v: string) => void;\n ariaLabel: string;\n}) {\n return (\n <input\n type=\"date\"\n aria-label={ariaLabel}\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-600\">{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-700 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=\"inline-flex min-h-12 items-center px-2 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-700\">\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-700\">\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-700 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=\"inline-flex min-h-12 items-center px-2 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 PDFs or other 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-700\">\n You'll add a title, author, and description for each file below.\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-700\">\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-700\"\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-700\">\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-700\">\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-700'\n : 'text-stone-600'\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-700'\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-700 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 // A <div> child of <dl> may contain ONLY dt/dd groups (HTML spec;\n // axe definition-list), so the index + Edit button live INSIDE\n // <dt>. Subgrid keeps all three columns aligned across dt and dd\n // without extra wrappers.\n <div\n key={q.id}\n className=\"grid grid-cols-[auto_1fr_auto] gap-x-4 border-b border-stone-200 pb-6\"\n >\n <dt className=\"col-span-3 grid grid-cols-subgrid\">\n <span aria-hidden=\"true\" className=\"pt-1 font-mono text-sm text-stone-600\">\n {String(i + 1).padStart(2, '0')}\n </span>\n <span className=\"text-sm font-medium text-stone-900\">{q.prompt}</span>\n <button\n type=\"button\"\n onClick={() => onEdit(i)}\n aria-label={`Edit: ${q.prompt}`}\n className=\"btn-ghost self-start text-sm\"\n >\n Edit\n </button>\n </dt>\n <dd className=\"col-span-3 mt-2 grid grid-cols-subgrid\">\n <span className=\"col-start-2 whitespace-pre-wrap text-stone-900\">\n {answered ? (\n display\n ) : (\n <span className=\"italic text-stone-600\">Not answered</span>\n )}\n </span>\n </dd>\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":";AA2BU,SAeA,KAfA;AAPH,SAAS,YAAY,EAAE,SAAS,OAAO,0BAA0B,GAAU;AAChF,QAAM,UAAU,KAAK,MAAO,UAAU,QAAS,GAAG;AAElD,SACE,oBAAC,SAAI,WAAU,oDACb,+BAAC,SAAI,WAAU,qBACb;AAAA,yBAAC,SAAI,WAAU,6CACb;AAAA,2BAAC,UAAK,WAAU,6DAA4D;AAAA;AAAA,QAChE;AAAA,QAAQ;AAAA,QAAK;AAAA,SACzB;AAAA,MACA,qBAAC,UAAK,WAAU,oCAAmC;AAAA;AAAA,QAC/C;AAAA,QAA0B;AAAA,SAC9B;AAAA,OACF;AAAA,IACA;AAAA,MAAC;AAAA;AAAA,QACC,WAAU;AAAA,QACV,MAAK;AAAA,QACL,iBAAe;AAAA,QACf,iBAAe;AAAA,QACf,iBAAe;AAAA,QACf,cAAY,uBAAuB,OAAO;AAAA,QAE1C;AAAA,UAAC;AAAA;AAAA,YACC,WAAU;AAAA,YACV,OAAO,EAAE,OAAO,GAAG,OAAO,IAAI;AAAA;AAAA,QAChC;AAAA;AAAA,IACF;AAAA,KACF,GACF;AAEJ;;;AChDA,SAAS,UAAU,iBAAiB;AA4G9B,gBAAAA,MA0BQ,QAAAC,aA1BR;AAxEN,SAAS,gBAAgB,UAA0C;AACjE,UAAQ,SAAS,WAAW;AAAA,IAC1B,KAAK;AAAA,IACL,KAAK;AACH,aAAO,CAAC;AAAA,IACV,KAAK;AACH,aAAO,CAAC;AAAA,IACV,KAAK;AAAA,IACL,KAAK;AACH,aAAO,CAAC;AAAA,IACV;AACE,aAAO;AAAA,EACX;AACF;AAMA,SAAS,eAAe,UAA6B,OAA6B;AAChF,MAAI,CAAC,SAAS,SAAU,QAAO;AAC/B,MAAI,UAAU,UAAa,UAAU,KAAM,QAAO;AAClD,MAAI,OAAO,UAAU,SAAU,QAAO,MAAM,KAAK,EAAE,SAAS;AAC5D,MAAI,MAAM,QAAQ,KAAK,EAAG,QAAO,MAAM,SAAS;AAChD,MAAI,OAAO,UAAU,SAAU,QAAO,OAAO,KAAK,KAAK,EAAE,SAAS;AAClE,SAAO;AACT;AAgBO,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAU;AACR,QAAM,CAAC,YAAY,aAAa,IAAI;AAAA,IAClC,kBAAkB,gBAAgB,QAAQ;AAAA,EAC5C;AAIA,YAAU,MAAM;AACd,kBAAc,kBAAkB,gBAAgB,QAAQ,CAAC;AAAA,EAC3D,GAAG,CAAC,SAAS,IAAI,gBAAgB,SAAS,SAAS,CAAC;AAEpD,QAAM,eAAe,CAAC,MAAuB;AAC3C,MAAE,eAAe;AACjB,mBAAe,SAAS,IAAI,UAAU;AACtC,WAAO;AAAA,EACT;AAEA,QAAM,aAAa,eAAe,UAAU,UAAU;AAEtD,SACE,gBAAAA,MAAC,UAAK,UAAU,cAAc,WAAU,oBAEtC;AAAA,oBAAAD,KAAC,QAAG,WAAU,gEACX,mBAAS,QACZ;AAAA,IACC,SAAS,aACR,gBAAAA,KAAC,OAAE,WAAU,+BAA+B,mBAAS,WAAU;AAAA,IAIjE,gBAAAA,KAAC,SAAI,WAAU,QACb,0BAAAA;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,OAAO;AAAA,QACP,UAAU;AAAA;AAAA,IACZ,GACF;AAAA,IAGC,SAAS,YAAY,SAAS,SAAS,SAAS,KAC/C,gBAAAC,MAAC,aAAQ,WAAU,QACjB;AAAA,sBAAAD,KAAC,aAAQ,WAAU,4EAA2E,iDAE9F;AAAA,MAGA,gBAAAA,KAAC,QAAG,WAAU,oDACX,mBAAS,SAAS,IAAI,CAAC,IAAI,MAC1B,gBAAAC,MAAC,QAAW,WAAU,iCAAgC;AAAA;AAAA,QAClD;AAAA,QAAG;AAAA,WADE,CAET,CACD,GACH;AAAA,OACF;AAAA,IAID,CAAC,SAAS,YAAY,SAAS,mBAC9B,gBAAAA,MAAC,OAAE,WAAU,+BACX;AAAA,sBAAAD,KAAC,UAAK,WAAU,eAAc,+BAAiB;AAAA,MAAO;AAAA,MAAE,SAAS;AAAA,OACnE;AAAA,IAIF,gBAAAC,MAAC,SAAI,WAAU,2CACb;AAAA,sBAAAD;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAS;AAAA,UACT,UAAU;AAAA,UACV,WAAU;AAAA,UACX;AAAA;AAAA,MAED;AAAA,MACA,gBAAAC,MAAC,SAAI,WAAU,cACZ;AAAA,SAAC,SAAS,YACT,gBAAAD,KAAC,YAAO,MAAK,UAAS,SAAS,QAAQ,WAAU,aAAY,kBAE7D;AAAA,QAEF,gBAAAA,KAAC,YAAO,MAAK,UAAS,UAAU,CAAC,YAAY,WAAU,eACpD,mBAAS,mBAAmB,mBAC/B;AAAA,SACF;AAAA,OACF;AAAA,KACF;AAEJ;AAYA,SAAS,aAAa,EAAE,UAAU,OAAO,SAAS,GAAe;AAC/D,UAAQ,SAAS,WAAW;AAAA,IAC1B,KAAK;AACH,aAAO,gBAAAA,KAAC,kBAAe,OAAwB,UAAoB;AAAA,IACrE,KAAK;AACH,aAAO,gBAAAA,KAAC,iBAAc,OAAwB,UAAoB;AAAA,IACpE,KAAK;AACH,aAAO,gBAAAA,KAAC,qBAAkB,UAAoB,OAAwB,UAAoB;AAAA,IAC5F,KAAK;AACH,aAAO,gBAAAA,KAAC,oBAAiB,UAAoB,OAAQ,SAAsB,CAAC,GAAG,UAAoB;AAAA,IACrG,KAAK;AACH,aAAO,gBAAAA,KAAC,aAAU,OAAQ,SAAsB,CAAC,GAAG,UAAoB,WAAW,SAAS,WAAW;AAAA,IACzG,KAAK;AACH,aAAO,gBAAAA,KAAC,cAAW,UAAoB,OAAwB,UAAoB;AAAA,IACrF,KAAK;AACH,aAAO,gBAAAA,KAAC,aAAU,OAAwB,UAAoB,WAAW,SAAS,QAAQ;AAAA,IAC5F,KAAK;AACH,aAAO,gBAAAA,KAAC,kBAAe,UAAoB,OAAQ,SAA6B,CAAC,GAAG,UAAoB;AAAA,IAC1G,KAAK;AACH,aAAO,gBAAAA,KAAC,iBAAc,UAAoB,OAAQ,SAA4B,CAAC,GAAG,UAAoB;AAAA,IACxG,KAAK;AACH,aAAO,gBAAAA,KAAC,mBAAgB,UAAoB,OAAQ,SAA8B,CAAC,GAAG,UAAoB;AAAA,IAC5G,SAAS;AACP,YAAM,cAAqB,SAAS;AACpC,aAAO,gBAAAA,KAAC,uBAAoB,WAAW,aAAa;AAAA,IACtD;AAAA,EACF;AACF;AAEA,SAAS,eAAe,EAAE,OAAO,SAAS,GAAqD;AAC7F,SACE,gBAAAA;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,OAAO,SAAS;AAAA,MAChB,UAAU,CAAC,MAAM,SAAS,EAAE,OAAO,KAAK;AAAA,MACxC,WAAS;AAAA,MACT,WAAU;AAAA,MACV,aAAY;AAAA;AAAA,EACd;AAEJ;AAEA,SAAS,cAAc,EAAE,OAAO,SAAS,GAAqD;AAC5F,SACE,gBAAAA;AAAA,IAAC;AAAA;AAAA,MACC,OAAO,SAAS;AAAA,MAChB,UAAU,CAAC,MAAM,SAAS,EAAE,OAAO,KAAK;AAAA,MACxC,WAAS;AAAA,MACT,MAAM;AAAA,MACN,WAAU;AAAA,MACV,aAAY;AAAA;AAAA,EACd;AAEJ;AAEA,SAAS,kBAAkB;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,SACE,gBAAAA,KAAC,SAAI,WAAU,aAAY,MAAK,cAAa,mBAAiB,KAAK,SAAS,EAAE,IAC1E,oBAAS,WAAW,CAAC,GAAG,IAAI,CAAC,QAC7B,gBAAAC;AAAA,IAAC;AAAA;AAAA,MAEC,WAAW,mFACT,UAAU,IAAI,QACV,mCACA,qDACN;AAAA,MAEA;AAAA,wBAAAD;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,MAAM,SAAS;AAAA,YACf,OAAO,IAAI;AAAA,YACX,SAAS,UAAU,IAAI;AAAA,YACvB,UAAU,CAAC,MAAM,SAAS,EAAE,OAAO,KAAK;AAAA,YACxC,WAAU;AAAA;AAAA,QACZ;AAAA,QACA,gBAAAC,MAAC,SACC;AAAA,0BAAAD,KAAC,SAAI,WAAU,8BAA8B,cAAI,OAAM;AAAA,UACtD,IAAI,eACH,gBAAAA,KAAC,SAAI,WAAU,+BAA+B,cAAI,aAAY;AAAA,WAElE;AAAA;AAAA;AAAA,IApBK,IAAI;AAAA,EAqBX,CACD,GACH;AAEJ;AAEA,SAAS,iBAAiB;AAAA,EACxB;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,SACE,gBAAAA,KAAC,SAAI,WAAU,aAAY,MAAK,SAAQ,mBAAiB,KAAK,SAAS,EAAE,IACrE,oBAAS,WAAW,CAAC,GAAG,IAAI,CAAC,QAAQ;AACrC,UAAM,UAAU,MAAM,SAAS,IAAI,KAAK;AACxC,WACE,gBAAAC;AAAA,MAAC;AAAA;AAAA,QAEC,WAAW,mFACT,UACI,mCACA,qDACN;AAAA,QAEA;AAAA,0BAAAD;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,OAAO,IAAI;AAAA,cACX;AAAA,cACA,UAAU,CAAC,MAAM;AACf,oBAAI,EAAE,OAAO,QAAS,UAAS,CAAC,GAAG,OAAO,IAAI,KAAK,CAAC;AAAA,oBAC/C,UAAS,MAAM,OAAO,CAAC,MAAM,MAAM,IAAI,KAAK,CAAC;AAAA,cACpD;AAAA,cACA,WAAU;AAAA;AAAA,UACZ;AAAA,UACA,gBAAAC,MAAC,SACC;AAAA,4BAAAD,KAAC,SAAI,WAAU,8BAA8B,cAAI,OAAM;AAAA,YACtD,IAAI,eACH,gBAAAA,KAAC,SAAI,WAAU,+BAA+B,cAAI,aAAY;AAAA,aAElE;AAAA;AAAA;AAAA,MAtBK,IAAI;AAAA,IAuBX;AAAA,EAEJ,CAAC,GACH;AAEJ;AAEA,SAAS,WAAW;AAAA,EAClB;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,QAAM,UAAU,SAAS,WAAW,CAAC;AACrC,SACE,gBAAAA,KAAC,SAAI,WAAU,aAAY,MAAK,cAAa,mBAAiB,KAAK,SAAS,EAAE,IAC3E,kBAAQ,IAAI,CAAC,QACZ,gBAAAC;AAAA,IAAC;AAAA;AAAA,MAEC,WAAW,oFACT,UAAU,IAAI,QACV,mCACA,qDACN;AAAA,MAEA;AAAA,wBAAAD;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,MAAM,SAAS;AAAA,YACf,OAAO,IAAI;AAAA,YACX,SAAS,UAAU,IAAI;AAAA,YACvB,UAAU,CAAC,MAAM,SAAS,EAAE,OAAO,KAAK;AAAA,YACxC,WAAU;AAAA;AAAA,QACZ;AAAA,QACA,gBAAAA,KAAC,UAAK,WAAU,oCAAoC,cAAI,OAAM;AAAA,QAC9D,gBAAAA,KAAC,UAAK,WAAU,kBAAkB,cAAI,OAAM;AAAA;AAAA;AAAA,IAhBvC,IAAI;AAAA,EAiBX,CACD,GACH;AAEJ;AAEA,SAAS,UAAU;AAAA,EACjB;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,SACE,gBAAAA;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,cAAY;AAAA,MACZ,OAAO,SAAS;AAAA,MAChB,UAAU,CAAC,MAAM,SAAS,EAAE,OAAO,KAAK;AAAA,MACxC,WAAS;AAAA,MACT,WAAU;AAAA;AAAA,EACZ;AAEJ;AAEA,SAAS,UAAU;AAAA,EACjB;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,QAAM,QAAQ,MAAM,SAAS,IAAI,QAAQ,CAAC,EAAE;AAC5C,QAAM,MAAM,WAAW,OAAO;AAC9B,QAAM,MAAM,WAAW,OAAO;AAE9B,QAAM,UAAU,CAAC,GAAW,MAAc;AACxC,UAAM,OAAO,CAAC,GAAG,KAAK;AACtB,SAAK,CAAC,IAAI;AACV,aAAS,IAAI;AAAA,EACf;AACA,QAAM,UAAU,MAAM;AACpB,QAAI,MAAM,SAAS,IAAK,UAAS,CAAC,GAAG,OAAO,EAAE,CAAC;AAAA,EACjD;AACA,QAAM,aAAa,CAAC,MAAc;AAChC,QAAI,MAAM,UAAU,EAAG;AACvB,aAAS,MAAM,OAAO,CAAC,GAAG,QAAQ,QAAQ,CAAC,CAAC;AAAA,EAC9C;AACA,QAAM,aAAa,MAAM,OAAO,CAAC,MAAM,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE;AAE5D,SACE,gBAAAC,MAAC,SACC;AAAA,oBAAAD,KAAC,QAAG,WAAU,aACX,gBAAM,IAAI,CAAC,MAAM,MAChB,gBAAAC,MAAC,QAAW,WAAU,2BACpB;AAAA,sBAAAA,MAAC,UAAK,WAAU,mDAAmD;AAAA,YAAI;AAAA,QAAE;AAAA,SAAC;AAAA,MAC1E,gBAAAD;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,OAAO;AAAA,UACP,UAAU,CAAC,MAAM,QAAQ,GAAG,EAAE,OAAO,KAAK;AAAA,UAC1C,WAAU;AAAA,UACV,aAAa,QAAQ,IAAI,CAAC;AAAA;AAAA,MAC5B;AAAA,MACA,gBAAAA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAS,MAAM,WAAW,CAAC;AAAA,UAC3B,UAAU,MAAM,UAAU;AAAA,UAC1B,WAAU;AAAA,UACV,cAAY,eAAe,IAAI,CAAC;AAAA,UACjC;AAAA;AAAA,MAED;AAAA,SAjBO,CAkBT,CACD,GACH;AAAA,IACA,gBAAAC,MAAC,SAAI,WAAU,0CACb;AAAA,sBAAAD;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAS;AAAA,UACT,UAAU,MAAM,UAAU;AAAA,UAC1B,WAAU;AAAA,UACX;AAAA;AAAA,MAED;AAAA,MACC,aACC,gBAAAC,MAAC,UAAK,WAAU,oCACb;AAAA;AAAA,QAAW;AAAA,QAAI;AAAA,QAAI;AAAA,QAAE,QAAQ,WAAW,WAAM;AAAA,SACjD;AAAA,OAEJ;AAAA,KACF;AAEJ;AAEA,SAAS,eAAe;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,QAAM,eAAe,SAAS,gBAAgB,CAAC;AAC/C,SACE,gBAAAD,KAAC,SAAI,WAAU,kEACZ,uBAAa,IAAI,CAAC,QACjB,gBAAAC,MAAC,SACC;AAAA,oBAAAD,KAAC,SAAI,WAAU,sCAAsC,cAAI,QAAO;AAAA,IAC/D,IAAI,aAAa,gBAAAA,KAAC,SAAI,WAAU,+BAA+B,cAAI,WAAU;AAAA,IAC9E,gBAAAA,KAAC,SAAI,WAAU,QACb,0BAAAA;AAAA,MAAC;AAAA;AAAA,QACC,UAAU;AAAA,QACV,OAAO,MAAM,IAAI,EAAE;AAAA,QACnB,UAAU,CAAC,SAAS,SAAS,EAAE,GAAG,OAAO,CAAC,IAAI,EAAE,GAAG,KAAK,CAAC;AAAA;AAAA,IAC3D,GACF;AAAA,OATQ,IAAI,EAUd,CACD,GACH;AAEJ;AAEA,SAAS,cAAc;AAAA,EACrB;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,QAAM,OAAO,MAAM,SAAS,IAAI,QAAQ,CAAC,CAAC,CAAgC;AAC1E,QAAM,aAAa,SAAS,cAAc,CAAC;AAC3C,QAAM,MAAM,SAAS,WAAW,OAAO;AACvC,QAAM,MAAM,SAAS,WAAW,OAAO;AAEvC,QAAM,YAAY,CAAC,GAAW,UAAuC;AACnE,UAAM,OAAO,CAAC,GAAG,IAAI;AACrB,SAAK,CAAC,IAAI,EAAE,GAAG,KAAK,CAAC,GAAG,GAAG,MAAM;AACjC,aAAS,IAAI;AAAA,EACf;AACA,QAAM,SAAS,MAAM;AACnB,QAAI,KAAK,SAAS,IAAK,UAAS,CAAC,GAAG,MAAM,CAAC,CAAgC,CAAC;AAAA,EAC9E;AACA,QAAM,YAAY,CAAC,MAAc;AAC/B,QAAI,KAAK,UAAU,EAAG;AACtB,aAAS,KAAK,OAAO,CAAC,GAAG,QAAQ,QAAQ,CAAC,CAAC;AAAA,EAC7C;AAEA,SACE,gBAAAC,MAAC,SAAI,WAAU,aACZ;AAAA,SAAK,IAAI,CAAC,KAAK,MACd,gBAAAA,MAAC,SAAY,WAAU,wDACrB;AAAA,sBAAAA,MAAC,SAAI,WAAU,0CACb;AAAA,wBAAAA,MAAC,UAAK,WAAU,6DAA4D;AAAA;AAAA,UACpE,IAAI;AAAA,WACZ;AAAA,QACA,gBAAAD;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAS,MAAM,UAAU,CAAC;AAAA,YAC1B,UAAU,KAAK,UAAU;AAAA,YACzB,WAAU;AAAA,YACV,cAAY,eAAe,IAAI,CAAC;AAAA,YACjC;AAAA;AAAA,QAED;AAAA,SACF;AAAA,MACA,gBAAAA,KAAC,SAAI,WAAU,aACZ,qBAAW,IAAI,CAAC,QACf,gBAAAC,MAAC,SACC;AAAA,wBAAAD,KAAC,SAAI,WAAU,sCAAsC,cAAI,QAAO;AAAA,QAC/D,IAAI,aACH,gBAAAA,KAAC,SAAI,WAAU,+BAA+B,cAAI,WAAU;AAAA,QAE9D,gBAAAA,KAAC,SAAI,WAAU,QACb,0BAAAA;AAAA,UAAC;AAAA;AAAA,YACC,UAAU;AAAA,YACV,OAAO,IAAI,IAAI,EAAE;AAAA,YACjB,UAAU,CAAC,SAAS,UAAU,GAAG,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,CAAC;AAAA;AAAA,QACrD,GACF;AAAA,WAXQ,IAAI,EAYd,CACD,GACH;AAAA,SA/BQ,CAgCV,CACD;AAAA,IACD,gBAAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAS;AAAA,QACT,UAAU,KAAK,UAAU;AAAA,QACzB,WAAU;AAAA,QACX;AAAA;AAAA,IAED;AAAA,KACF;AAEJ;AAEA,SAAS,gBAAgB;AAAA,EACvB;AAAA,EACA;AAAA,EACA;AACF,GAIG;AACD,QAAM,aAAa,SAAS,cAAc,CAAC;AAE3C,QAAM,UAAU,CAAC,UAA2B;AAC1C,QAAI,CAAC,SAAS,MAAM,WAAW,EAAG;AAClC,UAAM,UAAuB,MAAM,KAAK,KAAK,EAAE,IAAI,CAAC,OAAO;AAAA,MACzD,MAAM;AAAA,MACN,UAAU,CAAC;AAAA,IACb,EAAE;AACF,aAAS,CAAC,GAAG,OAAO,GAAG,OAAO,CAAC;AAAA,EACjC;AAEA,QAAM,iBAAiB,CAAC,GAAW,UAAuC;AACxE,UAAM,OAAO,CAAC,GAAG,KAAK;AACtB,SAAK,CAAC,IAAI,EAAE,GAAG,KAAK,CAAC,GAAG,UAAU,EAAE,GAAG,KAAK,CAAC,EAAE,UAAU,GAAG,MAAM,EAAE;AACpE,aAAS,IAAI;AAAA,EACf;AACA,QAAM,cAAc,CAAC,MAAc,SAAS,MAAM,OAAO,CAAC,GAAG,QAAQ,QAAQ,CAAC,CAAC;AAE/E,QAAM,SAAS,CAAC,MAAuC;AACrD,MAAE,eAAe;AACjB,YAAQ,EAAE,aAAa,KAAK;AAAA,EAC9B;AACA,QAAM,aAAa,CAAC,MAAuC,EAAE,eAAe;AAE5E,SACE,gBAAAC,MAAC,SAAI,WAAU,aACb;AAAA,oBAAAA;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA,WAAU;AAAA,QAEV;AAAA,0BAAAA,MAAC,OAAE,WAAU,0BAAyB;AAAA;AAAA,YACF;AAAA,YAClC,gBAAAA,MAAC,WAAM,WAAU,oEAAmE;AAAA;AAAA,cAElF,gBAAAD;AAAA,gBAAC;AAAA;AAAA,kBACC,MAAK;AAAA,kBACL,UAAQ;AAAA,kBACR,WAAU;AAAA,kBACV,UAAU,CAAC,MAAM,QAAQ,EAAE,OAAO,KAAK;AAAA;AAAA,cACzC;AAAA,eACF;AAAA,aACF;AAAA,UACA,gBAAAA,KAAC,OAAE,WAAU,+BAA8B,8EAE3C;AAAA;AAAA;AAAA,IACF;AAAA,IAEC,MAAM,SAAS,KACd,gBAAAA,KAAC,QAAG,WAAU,aACX,gBAAM,IAAI,CAAC,OAAO,MAAM;AACvB,YAAM,OAAO,MAAM,KAAK;AACxB,YAAM,OAAO,UAAU,MAAM,OAAO,MAAM,KAAK,OAAO;AACtD,aACE,gBAAAC,MAAC,QAAW,WAAU,wDACpB;AAAA,wBAAAA,MAAC,SAAI,WAAU,qCACb;AAAA,0BAAAA,MAAC,SACC;AAAA,4BAAAD,KAAC,SAAI,WAAU,8BAA8B,gBAAK;AAAA,YACjD,OAAO,SAAS,YACf,gBAAAC,MAAC,SAAI,WAAU,oCACX;AAAA,sBAAO,MAAM,QAAQ,CAAC;AAAA,cAAE;AAAA,eAC5B;AAAA,aAEJ;AAAA,UACA,gBAAAD;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAS,MAAM,YAAY,CAAC;AAAA,cAC5B,WAAU;AAAA,cACV,cAAY,UAAU,IAAI;AAAA,cAC3B;AAAA;AAAA,UAED;AAAA,WACF;AAAA,QACC,WAAW,SAAS,KACnB,gBAAAA,KAAC,SAAI,WAAU,kBACZ,qBAAW,IAAI,CAAC,QACf,gBAAAC,MAAC,SACC;AAAA,0BAAAD,KAAC,SAAI,WAAU,sCAAsC,cAAI,QAAO;AAAA,UAChE,gBAAAA,KAAC,SAAI,WAAU,QACb,0BAAAA;AAAA,YAAC;AAAA;AAAA,cACC,UAAU;AAAA,cACV,OAAO,MAAM,SAAS,IAAI,EAAE;AAAA,cAC5B,UAAU,CAAC,SAAS,eAAe,GAAG,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,CAAC;AAAA;AAAA,UAC1D,GACF;AAAA,aARQ,IAAI,EASd,CACD,GACH;AAAA,WAjCK,CAmCT;AAAA,IAEJ,CAAC,GACH;AAAA,KAEJ;AAEJ;AAEA,SAAS,oBAAoB,EAAE,UAAU,GAAyB;AAIhE,SACE,gBAAAC,MAAC,SAAI,WAAU,+EAA8E;AAAA;AAAA,IAC1D;AAAA,IACjC,gBAAAD,KAAC,UAAK,WAAU,aAAa,iBAAO,SAAS,GAAE;AAAA,KACjD;AAEJ;;;AC9pBO,SAAS,WAAW,OAA6B;AACtD,MAAI,UAAU,UAAa,UAAU,KAAM,QAAO;AAClD,MAAI,OAAO,UAAU,SAAU,QAAO,MAAM,KAAK,EAAE,SAAS;AAC5D,MAAI,MAAM,QAAQ,KAAK,EAAG,QAAO,MAAM,SAAS;AAChD,MAAI,OAAO,UAAU,SAAU,QAAO,OAAO,KAAK,KAAK,EAAE,SAAS;AAClE,SAAO;AACT;AAMO,SAAS,eAAe,UAA6B,OAAoB,SAAS,IAAY;AACnG,MAAI,CAAC,WAAW,KAAK,EAAG,QAAO;AAC/B,UAAQ,SAAS,WAAW;AAAA,IAC1B,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO,SAAS,OAAO,KAAK,GAAG,MAAM;AAAA,IACvC,KAAK,iBAAiB;AACpB,YAAM,IAAI,OAAO,KAAK;AACtB,YAAM,QAAQ,SAAS,SAAS,KAAK,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG;AAC5D,aAAO,SAAS,SAAS,GAAG,MAAM;AAAA,IACpC;AAAA,IACA,KAAK;AAAA,IACL,KAAK,QAAQ;AACX,YAAM,MAAM;AACZ,YAAM,SAAS,IAAI,IAAI,CAAC,MAAM;AAC5B,cAAM,QAAQ,SAAS,SAAS,KAAK,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG;AAC5D,eAAO,SAAS;AAAA,MAClB,CAAC;AACD,aAAO,SAAS,OAAO,KAAK,IAAI,GAAG,MAAM;AAAA,IAC3C;AAAA,IACA,KAAK,aAAa;AAEhB,YAAM,MAAM;AACZ,YAAM,OAAO,SAAS,gBAAgB,CAAC;AACvC,iBAAW,OAAO,MAAM;AACtB,cAAM,SAAS,IAAI,IAAI,EAAE;AACzB,YAAI,WAAW,MAAM,GAAG;AACtB,iBAAO,SAAS,eAAe,KAAK,QAAQ,MAAM,GAAG,MAAM;AAAA,QAC7D;AAAA,MACF;AACA,aAAO;AAAA,IACT;AAAA,IACA,KAAK,YAAY;AACf,YAAM,OAAO;AACb,aAAO,GAAG,KAAK,MAAM,QAAQ,KAAK,WAAW,IAAI,KAAK,GAAG;AAAA,IAC3D;AAAA,IACA,KAAK,eAAe;AAClB,YAAM,QAAQ;AACd,aAAO,GAAG,MAAM,MAAM,QAAQ,MAAM,WAAW,IAAI,KAAK,GAAG;AAAA,IAC7D;AAAA,IACA;AACE,aAAO;AAAA,EACX;AACF;AAOO,SAAS,cAAc,UAA6B,OAA4B;AACrF,MAAI,CAAC,WAAW,KAAK,EAAG,QAAO;AAC/B,UAAQ,SAAS,WAAW;AAAA,IAC1B,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO,OAAO,KAAK;AAAA,IACrB,KAAK,iBAAiB;AACpB,YAAM,IAAI,OAAO,KAAK;AACtB,YAAM,QAAQ,SAAS,SAAS,KAAK,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG;AAC5D,aAAO,SAAS;AAAA,IAClB;AAAA,IACA,KAAK;AAAA,IACL,KAAK,QAAQ;AACX,YAAM,MAAM;AACZ,YAAM,SAAS,IAAI,IAAI,CAAC,MAAM,SAAS,SAAS,KAAK,CAAC,MAAM,EAAE,UAAU,CAAC,GAAG,SAAS,CAAC;AACtF,aAAO,OAAO,KAAK,WAAM,EAAE,QAAQ,KAAK,SAAI;AAAA,IAC9C;AAAA,IACA,KAAK,aAAa;AAChB,YAAM,MAAM;AACZ,YAAM,OAAO,SAAS,gBAAgB,CAAC;AACvC,YAAM,QAAkB,CAAC;AACzB,iBAAW,OAAO,MAAM;AACtB,cAAM,SAAS,IAAI,IAAI,EAAE;AACzB,YAAI,WAAW,MAAM,GAAG;AACtB,gBAAM,KAAK,GAAG,IAAI,MAAM,KAAK,eAAe,KAAK,QAAQ,GAAG,CAAC,EAAE;AAAA,QACjE;AAAA,MACF;AACA,aAAO,MAAM,KAAK,IAAI;AAAA,IACxB;AAAA,IACA,KAAK,YAAY;AACf,YAAM,OAAO;AACb,YAAM,aAAa,SAAS,cAAc,CAAC;AAC3C,YAAM,UAAU,KAAK,IAAI,CAAC,KAAK,MAAM;AACnC,cAAM,QAAkB,CAAC,QAAQ,IAAI,CAAC,GAAG;AACzC,mBAAW,OAAO,YAAY;AAC5B,gBAAM,SAAS,IAAI,IAAI,EAAE;AACzB,cAAI,WAAW,MAAM,GAAG;AACtB,kBAAM,KAAK,KAAK,IAAI,MAAM,KAAK,eAAe,KAAK,QAAQ,GAAG,CAAC,EAAE;AAAA,UACnE;AAAA,QACF;AACA,eAAO,MAAM,KAAK,IAAI;AAAA,MACxB,CAAC;AACD,aAAO,QAAQ,KAAK,IAAI;AAAA,IAC1B;AAAA,IACA,KAAK,eAAe;AAClB,YAAM,QAAQ;AACd,aAAO,MACJ,IAAI,CAAC,GAAG,MAAM,GAAG,IAAI,CAAC,KAAK,EAAE,KAAK,IAAI,EAAE,EACxC,KAAK,IAAI;AAAA,IACd;AAAA,IACA;AACE,aAAO;AAAA,EACX;AACF;AAEA,SAAS,SAAS,GAAW,QAAwB;AACnD,SAAO,EAAE,SAAS,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC,IAAI,WAAM;AAC5D;;;AC5FM,gBAAAE,MAwBY,QAAAC,aAxBZ;AANC,SAAS,cAAc,EAAE,WAAW,cAAc,SAAS,iBAAiB,GAAU;AAC3F,SACE,gBAAAA;AAAA,IAAC;AAAA;AAAA,MACC,WAAU;AAAA,MACV,cAAW;AAAA,MAEX;AAAA,wBAAAD,KAAC,QAAG,WAAU,kEAAiE,0BAE/E;AAAA,QACA,gBAAAA,KAAC,QAAG,WAAU,aACX,oBAAU,IAAI,CAAC,GAAG,MAAM;AACvB,gBAAM,SAAS,QAAQ,EAAE,EAAE;AAC3B,gBAAM,WAAW,WAAW,MAAM;AAClC,gBAAM,YAAY,MAAM;AACxB,gBAAM,UAAU,WAAW,eAAe,GAAG,MAAM,IAAI;AAEvD,iBACE,gBAAAA,KAAC,QACC,0BAAAA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAS,MAAM,iBAAiB,CAAC;AAAA,cACjC,WAAW,4DACT,YACI,mCACA,WACA,wDACA,2DACN;AAAA,cAEA,0BAAAC,MAAC,SAAI,WAAU,0BACb;AAAA,gCAAAA;AAAA,kBAAC;AAAA;AAAA,oBACC,WAAW,4BACT,YACI,oBACA,WACA,mBACA,gBACN;AAAA,oBAEC;AAAA,6BAAO,IAAI,CAAC,EAAE,SAAS,GAAG,GAAG;AAAA,sBAAE;AAAA;AAAA;AAAA,gBAClC;AAAA,gBACA,gBAAAA,MAAC,SAAI,WAAU,aACb;AAAA,kCAAAD;AAAA,oBAAC;AAAA;AAAA,sBACC,WAAW,uBACT,WAAW,mBAAmB,gBAChC;AAAA,sBAEC,YAAE,OAAO,SAAS,KAAK,EAAE,OAAO,MAAM,GAAG,EAAE,IAAI,WAAM,EAAE;AAAA;AAAA,kBAC1D;AAAA,kBACC,YACC,gBAAAA,KAAC,SAAI,WAAU,mDACZ,mBACH;AAAA,mBAEJ;AAAA,iBACF;AAAA;AAAA,UACF,KAvCO,EAAE,EAwCX;AAAA,QAEJ,CAAC,GACH;AAAA;AAAA;AAAA,EACF;AAEJ;;;AC7DM,gBAAAE,MAuBQ,QAAAC,aAvBR;AAHC,SAAS,cAAc,EAAE,WAAW,SAAS,YAAY,QAAQ,WAAW,GAAU;AAC3F,SACE,gBAAAA,MAAC,SAAI,WAAU,oBACb;AAAA,oBAAAD,KAAC,QAAG,WAAU,gEAA+D,iCAE7E;AAAA,IACA,gBAAAA,KAAC,OAAE,WAAU,+BAA8B,wIAG3C;AAAA,IAEA,gBAAAA,KAAC,QAAG,WAAU,kDACX,oBAAU,IAAI,CAAC,GAAG,MAAM;AACvB,YAAM,SAAS,QAAQ,EAAE,EAAE;AAC3B,YAAM,WAAW,WAAW,MAAM;AAClC,YAAM,UAAU,WAAW,cAAc,GAAG,MAAM,IAAI;AAEtD;AAAA;AAAA;AAAA;AAAA;AAAA,QAKE,gBAAAC;AAAA,UAAC;AAAA;AAAA,YAEC,WAAU;AAAA,YAEV;AAAA,8BAAAA,MAAC,QAAG,WAAU,qCACZ;AAAA,gCAAAD,KAAC,UAAK,eAAY,QAAO,WAAU,yCAChC,iBAAO,IAAI,CAAC,EAAE,SAAS,GAAG,GAAG,GAChC;AAAA,gBACA,gBAAAA,KAAC,UAAK,WAAU,sCAAsC,YAAE,QAAO;AAAA,gBAC/D,gBAAAA;AAAA,kBAAC;AAAA;AAAA,oBACC,MAAK;AAAA,oBACL,SAAS,MAAM,OAAO,CAAC;AAAA,oBACvB,cAAY,SAAS,EAAE,MAAM;AAAA,oBAC7B,WAAU;AAAA,oBACX;AAAA;AAAA,gBAED;AAAA,iBACF;AAAA,cACA,gBAAAA,KAAC,QAAG,WAAU,0CACZ,0BAAAA,KAAC,UAAK,WAAU,kDACb,qBACC,UAEA,gBAAAA,KAAC,UAAK,WAAU,yBAAwB,0BAAY,GAExD,GACF;AAAA;AAAA;AAAA,UAzBK,EAAE;AAAA,QA0BT;AAAA;AAAA,IAEJ,CAAC,GACH;AAAA,IAKA,gBAAAC,MAAC,SAAI,WAAU,8DACb;AAAA,sBAAAD,KAAC,QAAG,WAAU,uCAAsC,+BAAiB;AAAA,MACrE,gBAAAA,KAAC,OAAE,WAAU,uBAAsB,kTAKnC;AAAA,MACA,gBAAAC,MAAC,OAAE,WAAU,+BACX;AAAA,wBAAAD,KAAC,UAAK,WAAU,eAAc,uBAAS;AAAA,QAAO;AAAA,SAGhD;AAAA,OACF;AAAA,IAEA,gBAAAA,KAAC,SAAI,WAAU,yBACb,0BAAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAS;AAAA,QACT,UAAU;AAAA,QACV,WAAU;AAAA,QAET,uBAAa,kCAA6B;AAAA;AAAA,IAC7C,GACF;AAAA,KACF;AAEJ;","names":["jsx","jsxs","jsx","jsxs","jsx","jsxs"]}
1
+ {"version":3,"sources":["../src/ProgressBar.tsx","../src/QuestionCard.tsx","../src/summarize.ts","../src/AnswerSidebar.tsx","../src/ReviewSummary.tsx"],"sourcesContent":["'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-700\">\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, useId } 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 * On-blur format validation (D.3 It-10). Returns the consumer's\n * author-language message when a non-empty string value fails the\n * consumer-supplied pattern; empty values are `required`'s concern. Fails\n * open on an invalid consumer regex (a content bug must not dead-end the\n * author).\n */\nfunction validationError(question: InterviewQuestion, value: AnswerValue): string | null {\n const v = question.validation;\n if (!v) return null;\n if (typeof value !== 'string' || value.trim().length === 0) return null;\n try {\n return new RegExp(v.pattern).test(value) ? null : v.message;\n } catch {\n return null;\n }\n}\n\n/**\n * Whether any validation error exists anywhere in the question's value —\n * top-level, composite sub-questions, or repeater/file-upload rows. Gates\n * Continue the same way required-empty does.\n */\nfunction hasValidationError(question: InterviewQuestion, value: AnswerValue): boolean {\n if (validationError(question, value) !== null) return true;\n if (question.inputType === 'composite' && question.subQuestions) {\n const rec = (value ?? {}) as AnswerComposite;\n return question.subQuestions.some((sub) => validationError(sub, rec[sub.id]) !== null);\n }\n if (\n (question.inputType === 'repeater' || question.inputType === 'file-upload') &&\n question.itemSchema\n ) {\n const rows = (value ?? []) as AnswerRepeater;\n return rows.some((row) =>\n question.itemSchema!.some((sub) => validationError(sub, row[sub.id] as AnswerValue) !== null),\n );\n }\n return false;\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 =\n canAdvanceWith(question, localValue) && !hasValidationError(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 {!question.required && (\n <span className=\"ml-3 inline-block translate-y-[-0.2em] rounded-full border border-stone-300 bg-stone-100 px-3 py-1 align-middle font-sans text-xs font-medium uppercase tracking-wider text-stone-700\">\n Optional\n </span>\n )}\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-700\">\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 (\n <ShortTextInput\n key={question.id}\n question={question}\n value={value as string}\n onChange={onChange}\n />\n );\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} ariaLabel={question.prompt} />;\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({\n question,\n value,\n onChange,\n}: {\n question?: InterviewQuestion;\n value: string;\n onChange: (v: string) => void;\n}) {\n // Errors surface on blur (not while typing), then clear live once the\n // value passes — validate-in-place per the skillful-coping rubric.\n const [blurred, setBlurred] = useState(false);\n const errorId = useId();\n const error = question ? validationError(question, value) : null;\n const showError = blurred && error !== null;\n return (\n <>\n <input\n type=\"text\"\n value={value ?? ''}\n onChange={(e) => onChange(e.target.value)}\n onBlur={() => setBlurred(true)}\n autoFocus\n aria-label={question?.prompt}\n aria-invalid={showError || undefined}\n aria-describedby={showError ? errorId : undefined}\n className={`w-full rounded-lg border-2 bg-stone-50 px-4 py-3 text-lg text-stone-900 focus:outline-none ${\n showError\n ? 'border-red-400 focus:border-red-700'\n : 'border-stone-200 focus:border-forest-700'\n }`}\n placeholder=\"Type your answer…\"\n />\n {showError && (\n <p id={errorId} className=\"mt-2 text-sm font-medium text-red-800\">\n {error}\n </p>\n )}\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-700\">{opt.value}</span>\n <span className=\"text-stone-900\">{opt.label}</span>\n </label>\n ))}\n </div>\n );\n}\n\nfunction DateInput({\n value,\n onChange,\n ariaLabel,\n}: {\n value: string;\n onChange: (v: string) => void;\n ariaLabel: string;\n}) {\n return (\n <input\n type=\"date\"\n aria-label={ariaLabel}\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-600\">{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-700 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=\"inline-flex min-h-12 items-center px-2 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-700\">\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-700\">\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-700 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=\"inline-flex min-h-12 items-center px-2 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 PDFs or other 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-700\">\n You'll add a title, author, and description for each file below.\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-700\">\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-700\"\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-700\">\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-700\">\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-700'\n : 'text-stone-600'\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-700'\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-700 line-clamp-1\">\n {preview}\n </div>\n )}\n {/* D.3 It-10: unanswered REQUIRED questions are the\n blockers to Generate — mark them so the author can\n tell at a glance which gaps matter. */}\n {!answered && q.required && (\n <div className=\"mt-1 text-xs font-medium text-amber-800\">\n <span aria-hidden=\"true\">●</span> Required — not yet answered\n </div>\n )}\n {!answered && !q.required && (\n <div className=\"mt-1 text-xs text-stone-600\">Optional</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 // A <div> child of <dl> may contain ONLY dt/dd groups (HTML spec;\n // axe definition-list), so the index + Edit button live INSIDE\n // <dt>. Subgrid keeps all three columns aligned across dt and dd\n // without extra wrappers.\n <div\n key={q.id}\n className=\"grid grid-cols-[auto_1fr_auto] gap-x-4 border-b border-stone-200 pb-6\"\n >\n <dt className=\"col-span-3 grid grid-cols-subgrid\">\n <span aria-hidden=\"true\" className=\"pt-1 font-mono text-sm text-stone-600\">\n {String(i + 1).padStart(2, '0')}\n </span>\n <span className=\"text-sm font-medium text-stone-900\">{q.prompt}</span>\n <button\n type=\"button\"\n onClick={() => onEdit(i)}\n aria-label={`Edit: ${q.prompt}`}\n className=\"btn-ghost self-start text-sm\"\n >\n Edit\n </button>\n </dt>\n <dd className=\"col-span-3 mt-2 grid grid-cols-subgrid\">\n <span className=\"col-start-2 whitespace-pre-wrap text-stone-900\">\n {answered ? (\n display\n ) : (\n <span className=\"italic text-stone-600\">Not answered</span>\n )}\n </span>\n </dd>\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":["jsx","jsxs","jsx","jsxs","jsx","jsxs"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dataimago/interview",
3
- "version": "0.2.0-alpha.5",
3
+ "version": "0.2.0-alpha.6",
4
4
  "description": "Domain-agnostic interview engine — React components + schema contract for AI-native onboarding flows. Extracted from dataimago-ai/apps/hub during iteration-3-B.1 (2026-05-07).",
5
5
  "license": "MIT",
6
6
  "repository": {