dsh-diagnostic-tutor 0.1.0
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/LICENSE +21 -0
- package/README.md +658 -0
- package/cordis.patch.yml +23 -0
- package/lib/api.js +413 -0
- package/lib/api.js.map +1 -0
- package/lib/client.js +2029 -0
- package/lib/client.js.map +1 -0
- package/lib/contract.js +14 -0
- package/lib/contract.js.map +1 -0
- package/lib/diagnosis.js +224 -0
- package/lib/diagnosis.js.map +1 -0
- package/lib/handoff.js +194 -0
- package/lib/handoff.js.map +1 -0
- package/lib/index.js +186 -0
- package/lib/index.js.map +1 -0
- package/lib/lesson.js +285 -0
- package/lib/lesson.js.map +1 -0
- package/lib/prompt.js +96 -0
- package/lib/prompt.js.map +1 -0
- package/lib/state.js +500 -0
- package/lib/state.js.map +1 -0
- package/lib/tools.js +994 -0
- package/lib/tools.js.map +1 -0
- package/lib/trust-fence.js +101 -0
- package/lib/trust-fence.js.map +1 -0
- package/lib/types/api.d.ts +62 -0
- package/lib/types/api.d.ts.map +1 -0
- package/lib/types/contract.d.ts +147 -0
- package/lib/types/contract.d.ts.map +1 -0
- package/lib/types/diagnosis.d.ts +116 -0
- package/lib/types/diagnosis.d.ts.map +1 -0
- package/lib/types/handoff.d.ts +141 -0
- package/lib/types/handoff.d.ts.map +1 -0
- package/lib/types/index.d.ts +71 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/lesson.d.ts +295 -0
- package/lib/types/lesson.d.ts.map +1 -0
- package/lib/types/prompt.d.ts +85 -0
- package/lib/types/prompt.d.ts.map +1 -0
- package/lib/types/state.d.ts +627 -0
- package/lib/types/state.d.ts.map +1 -0
- package/lib/types/tools.d.ts +38 -0
- package/lib/types/tools.d.ts.map +1 -0
- package/lib/types/trust-fence.d.ts +53 -0
- package/lib/types/trust-fence.d.ts.map +1 -0
- package/lib/types/udt.d.ts +95 -0
- package/lib/types/udt.d.ts.map +1 -0
- package/lib/types/vocabulary.d.ts +162 -0
- package/lib/types/vocabulary.d.ts.map +1 -0
- package/lib/udt.js +141 -0
- package/lib/udt.js.map +1 -0
- package/lib/vocabulary.js +182 -0
- package/lib/vocabulary.js.map +1 -0
- package/package.json +104 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","names":["useState","realApi.fetchOverview","realApi.fetchNode","realApi.fetchLesson","realApi.startFocus","realApi.reportObserved","realApi.downloadExport","realApi.resetState","useState","useRef","useCallback","useMemo"],"sources":["../src/client/blocks.tsx","../src/client/model.ts","../src/client/api.ts","../src/client/use-learning.ts","../src/client/app.tsx","../src/client/tab.tsx","../src/client/styles.ts","../src/client/index.tsx"],"sourcesContent":["/**\n * Block renderers.\n *\n * The registry is the whole extension story: a block type is a key in\n * `BLOCK_RENDERERS`, so Formula, Code, Comparison, Practice or Resource arrive\n * as one more entry and never as a rewrite of the lesson renderer.\n *\n * Unknown types are the interesting case. A lesson authored by a newer host\n * will contain blocks this build has never heard of, and that must degrade —\n * the block is announced by type and left legible, rather than throwing inside\n * React and blanking the whole panel.\n *\n * Note what is absent: no percentage, no score, no stars, no progress bar.\n * State is always a word from the skill's vocabulary plus a coloured mark.\n */\n\nimport { useState, type ReactNode } from 'react'\n\nimport type { Block, HandoffView, NextStepView } from '../contract.js'\n\n/** Props every renderer receives. */\nexport interface BlockRenderProps<B extends Block = Block> {\n block: B\n}\n\ntype Renderer = (props: BlockRenderProps) => ReactNode\n\n/**\n * Inline formatting: `**bold**`, `` `code` ``, and inline math.\n *\n * Math is handled because the teaching brain writes it by convention —\n * `\\(...\\)` inline and `\\[...\\]` display are the skill's own rule — and a\n * STEM surface that prints the delimiters verbatim is unreadable.\n *\n * This is *styling*, not typesetting: the span is set apart and given a\n * monospace face so the expression is legible. Real math rendering needs a\n * typesetter (KaTeX or MathML), which is a later decision, not a silent gap.\n */\nconst INLINE_PATTERN = /(\\*\\*[^*]+\\*\\*|`[^`]+`|\\\\\\([^)]*\\\\\\))/g\n\nfunction inline(text: string): ReactNode[] {\n return text\n .split(INLINE_PATTERN)\n .filter(Boolean)\n .map((part, index) => {\n if (part.startsWith('**') && part.endsWith('**')) {\n return <strong key={index}>{part.slice(2, -2)}</strong>\n }\n if (part.startsWith('`') && part.endsWith('`')) return <code key={index}>{part.slice(1, -1)}</code>\n if (part.startsWith('\\\\(') && part.endsWith('\\\\)')) {\n return (\n <span className=\"dt-math\" key={index}>\n {part.slice(2, -2)}\n </span>\n )\n }\n return part\n })\n}\n\n/** One line of a text body, classified. */\ntype Line =\n | { kind: 'heading'; level: 2 | 3; text: string }\n | { kind: 'bullet'; text: string }\n | { kind: 'ordered'; marker: string; text: string }\n | { kind: 'math'; text: string }\n | { kind: 'text'; text: string }\n\n/**\n * Classify one line.\n *\n * The teaching brain writes ordinary light markdown — `###` headings, `-`\n * bullets, `1.` steps — because that is how a person writes an explanation.\n * Printing the markers verbatim is what makes a lesson look like a Markdown\n * renderer instead of a lesson, so they are recognised here.\n *\n * This adds **no block type**: it is how the existing text and check blocks are\n * displayed, which is the part of the surface the learner actually reads.\n */\nfunction classify(line: string): Line {\n const heading = /^(#{2,3})\\s+(.*)$/.exec(line)\n if (heading) return { kind: 'heading', level: heading[1] === '##' ? 2 : 3, text: heading[2]! }\n // Display math is written with escaped delimiters: \\[...\\]\n const trimmed = line.trim()\n if (trimmed.startsWith('\\\\[') && trimmed.endsWith('\\\\]')) {\n return { kind: 'math', text: trimmed.slice(2, -2).trim() }\n }\n const bullet = /^\\s*[-*•]\\s+(.*)$/.exec(line)\n if (bullet) return { kind: 'bullet', text: bullet[1]! }\n const ordered = /^\\s*(\\d+)[.)]\\s+(.*)$/.exec(line)\n if (ordered) return { kind: 'ordered', marker: ordered[1]!, text: ordered[2]! }\n return { kind: 'text', text: line }\n}\n\n/** Group consecutive lines into runs that render as one element. */\nfunction toRuns(md: string): Line[][] {\n const runs: Line[][] = []\n let current: Line[] = []\n let currentKind: Line['kind'] | null = null\n const flush = (): void => {\n if (current.length > 0) runs.push(current)\n current = []\n currentKind = null\n }\n for (const raw of md.split(/\\n/)) {\n const line = raw.trimEnd()\n if (line.trim().length === 0) {\n flush()\n continue\n }\n const parsed = classify(line)\n // Three things join the run in progress: a wrapped prose line, and a bullet\n // or ordered step continuing a list of the same kind. Everything else —\n // headings, display math, a change of list kind — starts a new run, so a\n // heading never swallows the prose under it.\n const continues =\n parsed.kind === 'text'\n ? currentKind === 'text'\n : (parsed.kind === 'bullet' || parsed.kind === 'ordered') && currentKind === parsed.kind\n if (!continues) flush()\n current.push(parsed)\n currentKind = parsed.kind\n }\n flush()\n return runs\n}\n\n/**\n * Render a text body as a lesson rather than as source.\n *\n * Blank-line separated runs become paragraphs, `###` becomes a real heading,\n * `-` and `1.` become real lists, and `\\[...\\]` becomes a display-math panel.\n */\nfunction Paragraphs({ md }: { md: string }): ReactNode {\n return (\n <>\n {toRuns(md).map((run, index) => {\n const first = run[0]!\n if (first.kind === 'heading') {\n const Tag = first.level === 2 ? 'h3' : 'h4'\n return (\n <Tag className={`dt-md-h${first.level}`} key={index}>\n {inline(first.text)}\n </Tag>\n )\n }\n if (first.kind === 'math') {\n return (\n <div className=\"dt-math-block\" key={index}>\n {first.text}\n </div>\n )\n }\n if (first.kind === 'bullet') {\n return (\n <ul className=\"dt-md-ul\" key={index}>\n {run.map((line, inner) => (\n <li key={inner}>{inline(line.text)}</li>\n ))}\n </ul>\n )\n }\n if (first.kind === 'ordered') {\n return (\n <ol className=\"dt-md-ol\" key={index}>\n {run.map((line, inner) => (\n <li key={inner}>{inline(line.text)}</li>\n ))}\n </ol>\n )\n }\n return (\n <p className=\"dt-block-md\" key={index}>\n {inline(run.map((line) => line.text).join(' '))}\n </p>\n )\n })}\n </>\n )\n}\n\nfunction TextBlockView({ block }: BlockRenderProps): ReactNode {\n if (block.type !== 'text') return null\n return (\n <div className=\"dt-block dt-block-text\">\n <Paragraphs md={block.content.md} />\n </div>\n )\n}\n\nfunction ExampleBlockView({ block }: BlockRenderProps): ReactNode {\n if (block.type !== 'example') return null\n return (\n <div className=\"dt-block dt-block-example\">\n <p className=\"dt-block-label\">Worked example</p>\n <h4>{block.content.title}</h4>\n <ol>\n {block.content.steps.map((step, index) => (\n <li key={index}>{inline(step)}</li>\n ))}\n </ol>\n {block.content.takeaway !== undefined && (\n <p className=\"dt-takeaway\">{inline(block.content.takeaway)}</p>\n )}\n </div>\n )\n}\n\nfunction DiagramBlockView({ block }: BlockRenderProps): ReactNode {\n if (block.type !== 'diagram') return null\n const isMermaid = block.content.format === 'mermaid'\n return (\n <div className=\"dt-block dt-block-diagram\">\n <p className=\"dt-block-label\">Diagram</p>\n {isMermaid && (\n <p className=\"dt-caption\">\n Source shown as text — this build ships no <code>mermaid</code> renderer.\n </p>\n )}\n <pre className=\"dt-pre\">{block.content.spec}</pre>\n {block.content.caption !== undefined && <p className=\"dt-caption\">{block.content.caption}</p>}\n </div>\n )\n}\n\n/**\n * The check.\n *\n * Rendered as an invitation rather than a field: this surface has no input box,\n * so the block has to make it obvious that the next move is the learner's and\n * that it happens in the chat. The wording says so instead of leaving a dead\n * question sitting in a panel.\n */\nfunction CheckBlockView({ block }: BlockRenderProps): ReactNode {\n if (block.type !== 'check') return null\n return (\n <div className=\"dt-block dt-block-check\">\n <div className=\"dt-check-head\">\n <span className=\"dt-check-tag\">Your turn</span>\n <span className=\"dt-check-where\">answer in the chat →</span>\n </div>\n <div className=\"dt-check-body\">\n <Paragraphs md={block.content.prompt} />\n </div>\n {block.content.hint !== undefined && (\n <p className=\"dt-check-hint\">\n <span>Hint</span>\n {inline(block.content.hint)}\n </p>\n )}\n </div>\n )\n}\n\n/**\n * The registry. Adding a block type means adding one entry here.\n *\n * Deliberately a plain object rather than a switch: a `Map`/record makes the\n * extension point visible and keeps the fallback impossible to forget.\n */\nexport const BLOCK_RENDERERS: Record<string, Renderer> = {\n text: TextBlockView,\n example: ExampleBlockView,\n diagram: DiagramBlockView,\n check: CheckBlockView,\n}\n\n/** Shown for a block type this build does not know. Never throws. */\nfunction UnknownBlockView({ block }: BlockRenderProps): ReactNode {\n const type = (block as { type?: unknown }).type\n return (\n <div className=\"dt-block dt-block-unknown\">\n <strong>{typeof type === 'string' ? type : 'unknown'}</strong> block — this build has no\n renderer for it. Update the plugin to see it.\n </div>\n )\n}\n\n/**\n * Render one block, degrading for anything unrecognised.\n *\n * @param props.block - the block to render.\n * @returns the rendered block.\n */\nexport function BlockView({ block }: { block: Block }): ReactNode {\n const renderer = BLOCK_RENDERERS[block.type] ?? UnknownBlockView\n return <div data-block-type={block.type}>{renderer({ block })}</div>\n}\n\n/**\n * Render a whole lesson's blocks in order.\n *\n * @param props.blocks - the blocks to render.\n * @returns the rendered list.\n */\nexport function LessonBody({ blocks }: { blocks: Block[] }): ReactNode {\n return (\n <>\n {blocks.map((block) => (\n <BlockView key={block.id} block={block} />\n ))}\n </>\n )\n}\n\n/**\n * The recommendation card.\n *\n * The learner reads *why* before they move, and nothing moves until they press\n * the button — the tutor decides, the runtime stores, the learner chooses. That\n * ordering is the whole point of the card existing rather than an automatic\n * jump.\n *\n * Wording follows the decision: a move names where it goes, a stay says so\n * plainly. No percentage, no score, no completion estimate — how far along the\n * learner is lives in the node's state and evidence, and nowhere else.\n */\nexport function NextStepCard({\n nextStep,\n onContinue,\n busy,\n}: {\n nextStep: NextStepView\n onContinue: () => void\n busy?: boolean\n}): ReactNode {\n const moves = nextStep.targetNodeId !== null\n return (\n <div className=\"dt-next\">\n <p className=\"dt-next-label\">\n <span className=\"dt-next-arrow\" aria-hidden=\"true\">\n ↓\n </span>\n Next best step\n </p>\n <p className=\"dt-next-from\">\n <span className=\"dt-next-tick\" aria-hidden=\"true\">\n ✓\n </span>\n {nextStep.fromNodeTitle} — {nextStep.action.replace(/-/g, ' ')}\n </p>\n <p className=\"dt-next-target\">\n {moves ? (\n <>\n Next: <b>{nextStep.targetNodeTitle}</b>\n </>\n ) : (\n <>\n Next: <b>Stay on {nextStep.fromNodeTitle}</b>\n </>\n )}\n </p>\n <p className=\"dt-next-why\">\n <span className=\"dt-next-why-label\">Why:</span> {nextStep.reason}\n </p>\n <button type=\"button\" className=\"dt-primary\" onClick={onContinue} disabled={busy === true}>\n {busy === true ? 'Starting…' : moves ? 'Continue learning' : 'Continue'}\n </button>\n </div>\n )\n}\n\n/**\n * The handoff progress line.\n *\n * A model turn is not instant, and a silent wait is indistinguishable from a\n * broken button — so the wait is narrated: which stage it is in, how long it\n * has been, and how many attempts. When it goes quiet the learner gets a retry\n * rather than a dead end, and retrying **never touches the focus**: the record\n * is a statement about the wait, never about where they are.\n *\n * The elapsed time is shown because it is honest. A tutor that takes ninety\n * seconds should look like a tutor that takes ninety seconds, not like a hang.\n */\nexport function HandoffLine({\n handoff,\n onRetry,\n}: {\n handoff: HandoffView\n onRetry: () => void\n}): ReactNode {\n const retryable = handoff.phase === 'failed' || handoff.phase === 'stalled'\n const seconds = Math.round(handoff.elapsedMs / 1000)\n return (\n <div className=\"dt-handoff\" data-phase={handoff.phase}>\n <span className=\"dt-handoff-dot\" aria-hidden=\"true\" />\n <span className=\"dt-handoff-label\">{handoff.label}</span>\n <span className=\"dt-handoff-time\">{seconds}s</span>\n {handoff.attempts > 1 && <span className=\"dt-handoff-try\">attempt {handoff.attempts}</span>}\n {handoff.detail !== undefined && <span className=\"dt-handoff-detail\">{handoff.detail}</span>}\n {retryable && (\n <button type=\"button\" className=\"dt-handoff-retry\" onClick={onRetry}>\n Ask again\n </button>\n )}\n </div>\n )\n}\n\n/**\n * The learner's controls over their own data.\n *\n * Both promises this project makes about state are kept here: that it is\n * **visible and exportable**, and that it can be **deleted**. An export that\n * only existed as an API route would not be a promise kept, and a delete that\n * happened on one click would be a trap — so the second click is the\n * confirmation, inline, with no dialog to dismiss by reflex.\n */\nexport function DataFooter({\n onExport,\n onReset,\n busy,\n}: {\n onExport: () => void\n onReset: () => void\n busy?: boolean\n}): ReactNode {\n const [confirming, setConfirming] = useState(false)\n\n if (confirming) {\n return (\n <div className=\"dt-data dt-data-confirm\">\n <p className=\"dt-data-ask\">\n Delete your goal, your map, every lesson and all the evidence behind it? This cannot\n be undone.\n </p>\n <div className=\"dt-data-row\">\n <button\n type=\"button\"\n className=\"dt-danger\"\n onClick={() => {\n setConfirming(false)\n onReset()\n }}\n disabled={busy === true}\n >\n {busy === true ? 'Deleting…' : 'Yes, delete everything'}\n </button>\n <button type=\"button\" className=\"dt-quiet\" onClick={() => setConfirming(false)}>\n Cancel\n </button>\n </div>\n </div>\n )\n }\n\n return (\n <div className=\"dt-data\">\n <button type=\"button\" className=\"dt-quiet\" onClick={onExport} disabled={busy === true}>\n Export my data\n </button>\n <button type=\"button\" className=\"dt-quiet\" onClick={() => setConfirming(true)} disabled={busy === true}>\n Delete everything\n </button>\n <span className=\"dt-data-note\">\n Stored on this machine only. Nothing is sent anywhere.\n </span>\n </div>\n )\n}\n","/**\n * Pure map model for the panel.\n *\n * Kept free of React and of `fetch` so the interesting logic — turning a flat\n * node list into a tree, ordering it, labelling states — is testable in a plain\n * node environment. The component below it is then mostly rendering.\n */\n\nimport type { NodeView } from '../contract.js'\n\n/** One node plus its children, in render order. */\nexport interface MapTreeNode {\n node: NodeView\n children: MapTreeNode[]\n depth: number\n}\n\n/**\n * Build the map tree from a flat node list.\n *\n * Ordering is insertion order within each level, which is the order the nodes\n * were diagnosed in — the map reads as a history, not as a syllabus.\n *\n * Nodes whose parent is missing are promoted to roots rather than dropped: a\n * dangling reference is a data problem, and silently hiding the node would be\n * worse than showing it at the top level.\n *\n * @param nodes - the flat node views.\n * @returns the roots, each with its subtree.\n */\nexport function buildMapTree(nodes: readonly NodeView[]): MapTreeNode[] {\n const byId = new Map<string, NodeView>()\n for (const node of nodes) byId.set(node.id, node)\n\n const childrenOf = new Map<string | null, NodeView[]>()\n for (const node of nodes) {\n const parent = node.parentId !== null && byId.has(node.parentId) ? node.parentId : null\n const bucket = childrenOf.get(parent) ?? []\n bucket.push(node)\n childrenOf.set(parent, bucket)\n }\n\n const rendered = new Set<string>()\n\n const build = (parentId: string | null, depth: number, ancestors: ReadonlySet<string>): MapTreeNode[] =>\n (childrenOf.get(parentId) ?? [])\n // A cycle would otherwise recurse forever. The store forbids one, but the\n // panel must not hang if it ever meets one.\n .filter((node) => !ancestors.has(node.id))\n .map((node) => {\n rendered.add(node.id)\n const nextAncestors = new Set(ancestors).add(node.id)\n return { node, depth, children: build(node.id, depth + 1, nextAncestors) }\n })\n\n const roots = build(null, 0, new Set())\n\n // Anything unreachable from a root is rendered as a root of its own. Without\n // this, a node caught in a parent cycle is never visited and disappears from\n // the panel entirely — dropping a node silently is worse than showing it in\n // the wrong place.\n for (const node of nodes) {\n if (rendered.has(node.id)) continue\n rendered.add(node.id)\n roots.push({ node, depth: 0, children: build(node.id, 1, new Set([node.id])) })\n }\n\n return roots\n}\n\n/**\n * Flatten a tree back into render order, carrying depth.\n *\n * @param roots - the tree.\n * @returns every node in top-down, left-to-right order.\n */\nexport function flattenTree(roots: readonly MapTreeNode[]): MapTreeNode[] {\n const out: MapTreeNode[] = []\n const walk = (nodes: readonly MapTreeNode[]): void => {\n for (const entry of nodes) {\n out.push(entry)\n walk(entry.children)\n }\n }\n walk(roots)\n return out\n}\n\n/** Human label for a relation, in the map's own terms. */\nexport function relationLabel(relation: string): string {\n switch (relation) {\n case 'goal':\n return 'goal'\n case 'part-of':\n return 'part of'\n case 'prerequisite':\n return 'prerequisite'\n case 'related':\n return 'related'\n default:\n return relation\n }\n}\n\n/**\n * Only `confirmed` reads as a filled mark; every other state is an outline.\n *\n * There are deliberately no numeric or graded progressions here — the seven\n * words are the entire vocabulary.\n */\nexport function isFilledState(state: string): boolean {\n return state === 'confirmed'\n}\n\n/** Short, non-learner-facing explanation of what a state means, for the detail pane. */\nexport function stateExplanation(state: string): string {\n switch (state) {\n case 'unconfirmed':\n return 'No evidence yet — nothing has been observed for this node.'\n case 'explained':\n return 'It has been explained, but that alone does not prove understanding.'\n case 'practiced':\n return 'Something was attempted at least once.'\n case 'checked':\n return 'A check or near-transfer question was asked.'\n case 'weak':\n return 'Partial or unstable — worth repairing before moving on.'\n case 'blocked':\n return 'Cannot proceed: something here is missing or misunderstood.'\n case 'confirmed':\n return 'Supported by a check or transfer observation. Re-checked, not assumed.'\n default:\n return 'Unknown state.'\n }\n}\n\n/** The seven states, in the order the skill introduces them. */\nexport const STATE_ORDER = [\n 'unconfirmed',\n 'explained',\n 'practiced',\n 'checked',\n 'weak',\n 'blocked',\n 'confirmed',\n] as const\n","/**\n * The browser half's HTTP client.\n *\n * Plain `fetch` against this plugin's own prefix. No storage access, no DSH\n * internals — the browser has exactly the three calls the panel makes.\n *\n * Every response is checked for the `{ ok }` envelope, so an HTTP 200 carrying\n * a failure cannot be mistaken for data.\n */\n\nimport type {\n FocusResponse,\n HandoffView,\n LessonResponse,\n NodeDetailResponse,\n OverviewResponse,\n} from '../contract.js'\n\n/** Route prefix owned by this plugin; must match the host's `API_PREFIX`. */\nconst BASE = '/diagnostic-tutor/api'\n\n/** Requests are local; anything slower than this is a problem worth surfacing. */\nconst TIMEOUT_MS = 8000\n\nasync function request<T>(path: string, init?: RequestInit): Promise<T> {\n const controller = new AbortController()\n const timer = setTimeout(() => controller.abort(), TIMEOUT_MS)\n try {\n const response = await fetch(`${BASE}${path}`, {\n ...init,\n signal: controller.signal,\n headers: { accept: 'application/json', ...(init?.headers ?? {}) },\n })\n\n let body: unknown\n try {\n body = await response.json()\n } catch {\n throw new Error(`unreadable response (HTTP ${response.status})`)\n }\n\n // Narrow by inspection rather than by generic: `T` is unconstrained, so a\n // union type would not discriminate.\n const envelope = body as { ok?: unknown; error?: { code?: string; message?: string } }\n if (!response.ok || envelope.ok === false) {\n throw new Error(\n envelope.error?.message ?? envelope.error?.code ?? `HTTP ${response.status}`,\n )\n }\n return body as T\n } finally {\n clearTimeout(timer)\n }\n}\n\n/** The current course and its whole diagnosis map. */\nexport function fetchOverview(): Promise<OverviewResponse> {\n return request<OverviewResponse>('/overview')\n}\n\n/** One node, its evidence, its parent and its children. */\nexport function fetchNode(nodeId: string): Promise<NodeDetailResponse> {\n return request<NodeDetailResponse>(`/node?id=${encodeURIComponent(nodeId)}`)\n}\n\n/** The lesson the tutor has written for a node; `lesson: null` until it has. */\nexport function fetchLesson(nodeId: string): Promise<LessonResponse> {\n return request<LessonResponse>(`/lesson?nodeId=${encodeURIComponent(nodeId)}`)\n}\n\n/**\n * Save everything the learner owns to a file.\n *\n * Deliberately a full download rather than a call that returns data: the point\n * of an export is that the learner ends up holding it, and a file they can move,\n * read and keep is the only version of that which survives this app.\n *\n * @returns the filename the browser was asked to save.\n */\nexport async function downloadExport(): Promise<string> {\n const response = await fetch(`${BASE}/export`, { headers: { accept: 'application/json' } })\n if (!response.ok) throw new Error(`export failed (HTTP ${response.status})`)\n const text = await response.text()\n\n const stamp = new Date().toISOString().slice(0, 10)\n const name = `dsh-diagnostic-tutor-${stamp}.json`\n const url = URL.createObjectURL(new Blob([text], { type: 'application/json' }))\n try {\n const anchor = document.createElement('a')\n anchor.href = url\n anchor.download = name\n document.body.appendChild(anchor)\n anchor.click()\n anchor.remove()\n } finally {\n // Revoking immediately is safe because the click has already handed the\n // blob to the browser's download machinery.\n URL.revokeObjectURL(url)\n }\n return name\n}\n\n/**\n * Delete everything and return to first run.\n *\n * @returns nothing; the caller re-reads the overview.\n */\nexport function resetState(): Promise<{ ok: true; reset: true }> {\n return request<{ ok: true; reset: true }>('/reset', { method: 'POST' })\n}\n\n/**\n * Tell the host a surface has rendered the lesson.\n *\n * The last leg of the timing chain: everything before it is measured on the\n * host, and this is the only part only the browser can answer.\n */\nexport function reportObserved(nodeId: string): Promise<{ handoff: HandoffView | null }> {\n return request<{ handoff: HandoffView | null }>('/handoff/observed', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ nodeId }),\n })\n}\n\n/**\n * What the Start learning button does.\n *\n * Records the focus and asks the host to wake the tutor. `sessionId` is the\n * session the panel is showing; without it the focus is still recorded and the\n * response says the tutor was not reached.\n */\nexport function startFocus(nodeId: string, sessionId?: string): Promise<FocusResponse> {\n return request<FocusResponse>('/focus', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify(sessionId === undefined ? { nodeId } : { nodeId, sessionId }),\n })\n}\n","/**\n * Shared learning state for both surfaces.\n *\n * The plugin renders the same runtime in two places — the full-page panel and\n * the docked right-sidebar tab — and both need identical behaviour: select a\n * node, start learning, follow the tutor. Duplicating that would mean two\n * implementations of the focus hand-off and two copies of the polling loop,\n * which is exactly how two surfaces drift apart.\n *\n * So the behaviour lives here once, and the components are presentation.\n */\n\nimport { useCallback, useEffect, useRef, useState } from 'react'\n\nimport type {\n FocusView,\n HandoffView,\n LessonRecord,\n NextStepView,\n NodeDetailResponse,\n OverviewResponse,\n} from '../contract.js'\nimport * as realApi from './api.js'\n\n/** The calls the surfaces make; injectable so preview and tests can fake them. */\nexport interface PanelClient {\n fetchOverview(): Promise<OverviewResponse>\n fetchNode(nodeId: string): Promise<NodeDetailResponse>\n fetchLesson(nodeId: string): Promise<LessonResponse>\n startFocus(nodeId: string, sessionId?: string): Promise<FocusResponse>\n /** Optional: only the real client can report it. */\n reportObserved?(nodeId: string): Promise<unknown>\n /** Optional: only the real client can save a file. */\n downloadExport?(): Promise<string>\n /** Optional: only the real client can delete state. */\n resetState?(): Promise<unknown>\n}\n\ntype LessonResponse = Awaited<ReturnType<typeof realApi.fetchLesson>>\ntype FocusResponse = Awaited<ReturnType<typeof realApi.startFocus>>\n\nexport const defaultClient: PanelClient = {\n fetchOverview: realApi.fetchOverview,\n fetchNode: realApi.fetchNode,\n fetchLesson: realApi.fetchLesson,\n startFocus: realApi.startFocus,\n reportObserved: realApi.reportObserved,\n downloadExport: realApi.downloadExport,\n resetState: realApi.resetState,\n}\n\n/**\n * How often the surfaces re-read state while a focus is active.\n *\n * Polling rather than a push channel: DSH exposes no generic host→client push\n * for third-party plugins, and the published UI plugins poll for the same\n * reason. The interval only runs while something is being learned, so an idle\n * surface makes no requests at all.\n */\nexport const POLL_INTERVAL_MS = 2000\n\nexport interface LearningState {\n readonly overview: OverviewResponse | null\n readonly focus: FocusView | null\n /** The tutor's recommendation, when the learner has not acted on it yet. */\n readonly nextStep: NextStepView | null\n /** How the current handoff is going, for the progress line. */\n readonly handoff: HandoffView | null\n /**\n * Whether a teaching brain is installed: `true`, confidently `false`, or\n * `null` when this scope cannot see the catalog. Only `false` may warn.\n */\n readonly teachingBrain: boolean | null\n /** The node whose detail is shown; the focus when nothing is picked. */\n readonly selectedId: string | null\n readonly detail: NodeDetailResponse | null\n readonly lesson: LessonRecord | null\n readonly note: string | null\n readonly error: string | null\n readonly starting: boolean\n readonly loading: boolean\n select(nodeId: string): void\n /** Start learning on the selected node. */\n start(): void\n /** Start learning on a named node — how a recommendation is acted on. */\n continueTo(nodeId: string): void\n dismissLesson(): void\n /** Save everything to a file. */\n exportData(): void\n /** Delete everything and re-read from empty. */\n resetAll(): void\n /** A one-line account of the last data action, for the surface to show. */\n readonly dataNote: string | null\n}\n\n/**\n * Read and follow the learning runtime.\n *\n * @param options.client - the API to talk to.\n * @param options.sessionId - the session the tutor should be woken in.\n * @param options.initialOverview - pre-supplied state, so the preview and tests\n * skip the first fetch.\n * @returns the state and the two actions the surfaces offer.\n */\nexport function useLearning(options: {\n client: PanelClient\n sessionId?: string | undefined\n initialOverview?: OverviewResponse | undefined\n /** Called once a focus has been recorded, so a surface can react to it. */\n onStarted?: (() => void) | undefined\n}): LearningState {\n const { client, sessionId, initialOverview, onStarted } = options\n\n const [overview, setOverview] = useState<OverviewResponse | null>(initialOverview ?? null)\n const [selectedId, setSelectedId] = useState<string | null>(null)\n const [detail, setDetail] = useState<NodeDetailResponse | null>(null)\n const [lesson, setLesson] = useState<LessonRecord | null>(null)\n const [note, setNote] = useState<string | null>(null)\n const [error, setError] = useState<string | null>(null)\n const [starting, setStarting] = useState(false)\n const [loading, setLoading] = useState(initialOverview === undefined)\n\n const focus = overview?.focus ?? null\n const focusNodeId = focus?.nodeId ?? null\n\n // Which node's detail is worth keeping fresh. The focus wins, so the panel\n // follows the tutor's node even when the learner is browsing elsewhere.\n const liveNodeId = focusNodeId ?? selectedId\n const liveRef = useRef<string | null>(liveNodeId)\n liveRef.current = liveNodeId\n /** The node whose lesson has already been reported as seen. */\n const observedRef = useRef<string | null>(null)\n\n useEffect(() => {\n if (initialOverview !== undefined) return\n let live = true\n client\n .fetchOverview()\n .then((data) => {\n if (live) setOverview(data)\n })\n .catch((cause: Error) => {\n if (live) setError(cause.message)\n })\n .finally(() => {\n if (live) setLoading(false)\n })\n return () => {\n live = false\n }\n }, [client, initialOverview])\n\n const select = useCallback(\n (nodeId: string) => {\n setSelectedId(nodeId)\n setError(null)\n client\n .fetchNode(nodeId)\n .then(setDetail)\n .catch((cause: Error) => setError(cause.message))\n },\n [client],\n )\n\n const beginFocus = useCallback(\n (nodeId: string) => {\n setStarting(true)\n setError(null)\n setNote(null)\n client\n .startFocus(nodeId, sessionId)\n .then(async (result) => {\n setNote(\n result.prompted\n ? 'Started. The tutor is teaching this node in the chat — the surface updates as it writes.'\n : `Focus recorded, but the tutor was not woken (${result.promptReason ?? 'unknown reason'}). Say anything in the chat to continue.`,\n )\n // Re-read the overview rather than trusting the response: the focus\n // lives in stored state, and the surfaces follow stored state. Without\n // this the panel would never see its own focus, and the polling effect\n // below — which keys off it — would never start.\n const [nextOverview, nextLesson] = await Promise.all([\n client.fetchOverview(),\n client.fetchLesson(result.focus.nodeId),\n ])\n setOverview(nextOverview)\n setLesson(nextLesson.lesson)\n onStarted?.()\n })\n .catch((cause: Error) => setError(cause.message))\n .finally(() => setStarting(false))\n },\n [client, sessionId, onStarted],\n )\n\n const start = useCallback(() => {\n if (selectedId === null) return\n beginFocus(selectedId)\n }, [beginFocus, selectedId])\n\n const [dataNote, setDataNote] = useState<string | null>(null)\n\n /** Re-read everything, the same way the initial load does. */\n const refresh = useCallback(\n () =>\n client\n .fetchOverview()\n .then((data) => {\n setOverview(data)\n setError(null)\n return data\n })\n .catch((cause: Error) => {\n setError(cause.message)\n return null\n }),\n [client],\n )\n\n const exportData = useCallback(() => {\n setDataNote(null)\n void client\n .downloadExport?.()\n .then((name) => setDataNote(`Saved ${name}.`))\n .catch((cause: Error) => setDataNote(`Could not export: ${cause.message}`))\n }, [client])\n\n const resetAll = useCallback(() => {\n setDataNote(null)\n setDetail(null)\n setLesson(null)\n void client\n .resetState?.()\n .then(refresh)\n .then(() => setDataNote('Everything was deleted.'))\n .catch((cause: Error) => setDataNote(`Could not delete: ${cause.message}`))\n }, [client, refresh])\n\n useEffect(() => {\n if (focus === null) return\n const nodeId = focus.nodeId\n let live = true\n\n const tick = async (): Promise<void> => {\n try {\n const [nextOverview, nextLesson] = await Promise.all([\n client.fetchOverview(),\n client.fetchLesson(nodeId),\n ])\n if (!live) return\n setOverview(nextOverview)\n setLesson(nextLesson.lesson)\n // The evidence trail and the node's state are what a check changes.\n const watched = liveRef.current\n if (watched !== null) setDetail(await client.fetchNode(watched))\n\n // Tell the host the lesson reached a screen. Reported once per node, so\n // the timing chain records the first sighting rather than every poll.\n if (nextLesson.lesson !== null && observedRef.current !== nodeId) {\n observedRef.current = nodeId\n void client.reportObserved?.(nodeId)?.catch(() => {})\n }\n } catch {\n // A failed poll is not worth surfacing; the next one may succeed.\n }\n }\n\n void tick()\n const timer = setInterval(() => void tick(), POLL_INTERVAL_MS)\n return () => {\n live = false\n clearInterval(timer)\n }\n }, [client, focus])\n\n return {\n overview,\n focus,\n nextStep: overview?.nextStep ?? null,\n handoff: overview?.handoff ?? null,\n teachingBrain: overview?.teachingBrain ?? null,\n selectedId,\n detail,\n lesson,\n note,\n error,\n starting,\n loading,\n select,\n start,\n continueTo: beginFocus,\n exportData,\n resetAll,\n dataNote,\n dismissLesson: useCallback(() => setLesson(null), []),\n }\n}\n","/**\n * The full-page learning panel.\n *\n * Three columns, left to right, that read as one sentence: **the course and its\n * diagnosis map → the node you picked → the learning surface for it.** This is\n * the \"focus mode\" view — the whole runtime at once.\n *\n * The docked tab (`tab.tsx`) renders the same state in the right sidebar so the\n * learner can keep the chat open while working. Both run on `useLearning`, so\n * they cannot disagree about what is focused or what the tutor wrote.\n *\n * ---------------------------------------------------------------------------\n * Who decides what\n * ---------------------------------------------------------------------------\n * The panel never writes teaching content and never decides what happens next.\n * Pressing **Start learning** records a focus and wakes the tutor; the blocks\n * that appear are whatever the tutor wrote through `udt_lesson_update`; the\n * evidence and state changes are whatever the tutor recorded through\n * `udt_map_update`. The panel's whole job is to show the current truth and to\n * keep showing it as it changes.\n */\n\nimport type { ReactNode } from 'react'\n\nimport { DataFooter, HandoffLine, LessonBody, NextStepCard } from './blocks.jsx'\nimport { buildMapTree, flattenTree, isFilledState, relationLabel, stateExplanation } from './model.js'\nimport type { PanelClient } from './use-learning.js'\nimport { defaultClient, useLearning } from './use-learning.js'\n\nexport type { PanelClient } from './use-learning.js'\n\nexport interface LearningPanelProps {\n /** Defaults to the real HTTP client. */\n client?: PanelClient\n /** Pre-supplied overview, so the preview and tests skip the first fetch. */\n initialOverview?: Parameters<typeof useLearning>[0]['initialOverview']\n /** The session the tutor should be woken in, when the host half knows one. */\n sessionId?: string | undefined\n /**\n * Show the conversation again.\n *\n * This panel fills the main column, which is also where the chat lives, so\n * answering a check means leaving it. Supplied by the client plugin through\n * the layout service; absent in the preview and in tests.\n */\n onOpenChat?: (() => void) | undefined\n /** Called once a focus is recorded — used to dock the side tab. */\n onFocusStarted?: (() => void) | undefined\n}\n\n/** The status pill plus its mark. Never a number. */\nfunction StateMark({ state }: { state: string }): ReactNode {\n return (\n <>\n <span className={`dt-dot dt-state-${state}`} data-filled={isFilledState(state)} />\n <span className={`dt-state dt-state-${state}`}>{state}</span>\n </>\n )\n}\n\n/**\n * The panel.\n *\n * @param props - injectable client, optional pre-supplied overview, session id.\n * @returns the three-pane learning surface.\n */\nexport function LearningPanel({\n client,\n initialOverview,\n sessionId,\n onOpenChat,\n onFocusStarted,\n}: LearningPanelProps): ReactNode {\n const state = useLearning({\n client: client ?? defaultClient,\n sessionId,\n initialOverview,\n onStarted: onFocusStarted,\n })\n const { overview, focus, nextStep, handoff, teachingBrain, dataNote, selectedId, detail, lesson, note, error, starting, loading } =\n state\n const rows = flattenTree(buildMapTree(overview?.nodes ?? []))\n const confirmed = (overview?.nodes ?? []).filter((node) => node.state === 'confirmed').length\n\n // First use, same as the docked tab: a blank three-column page reads as a\n // broken page. One question and the sentence that starts everything.\n if (overview?.course == null) {\n return (\n <div className=\"dt-root dt-root-welcome\">\n <div className=\"dt-welcome\">\n <p className=\"dt-welcome-eyebrow\">Universal Diagnostic Tutor</p>\n <h1 className=\"dt-welcome-title\">What do you want to learn?</h1>\n <p className=\"dt-welcome-body\">\n Works with the Universal Diagnostic Tutor skill. Start by telling the tutor what you\n want to learn — in your own words, in the chat. Then this page fills in: a map of what\n you actually know, the lesson, and the evidence behind every status.\n </p>\n <div className=\"dt-welcome-sample\">\n <span className=\"dt-welcome-sample-label\">Try</span>\n <span className=\"dt-welcome-sample-text\">\n I want to learn machine learning. I know some Python, but my math is weak.\n </span>\n </div>\n <p className=\"dt-welcome-note\">\n If tutoring doesn't start, make sure the UDT skill is available to your active DSH\n agent.\n </p>\n <p className=\"dt-welcome-note\">\n No account, no scores, no streak. Your goal, your map and the evidence behind it stay\n on this machine, and are yours to keep or delete.\n </p>\n <DataFooter onExport={state.exportData} onReset={state.resetAll} />\n {teachingBrain === false && (\n <p className=\"dt-notice\">\n <b>The Universal Diagnostic Tutor skill was not found.</b> This panel still records\n and shows your learning state, but no lesson will be written until the skill is\n available to your active DSH agent.\n </p>\n )}\n </div>\n </div>\n )\n }\n\n return (\n <div className=\"dt-root\">\n {/* ---- course + map ---- */}\n <div className=\"dt-pane\">\n <p className=\"dt-eyebrow\">Current course</p>\n <h1 className=\"dt-course-title\">{overview?.course?.title ?? 'No goal yet'}</h1>\n {overview?.course != null && (\n <p className=\"dt-goal\">\n <b>Goal:</b> {overview.course.goal}\n </p>\n )}\n\n <p className=\"dt-section-title\">\n Diagnosis map <span>{rows.length} nodes</span>\n </p>\n {rows.length === 0 && (\n <p className=\"dt-empty\">\n Nothing on the map yet. State a goal in the chat and the runtime will record it.\n </p>\n )}\n {rows.map(({ node, depth }) => (\n <button\n key={node.id}\n type=\"button\"\n className=\"dt-node\"\n style={{ marginLeft: `${depth * 12}px` }}\n aria-current={node.id === selectedId}\n data-focused={node.id === focus?.nodeId}\n onClick={() => state.select(node.id)}\n >\n <span className={`dt-dot dt-state-${node.state}`} data-filled={isFilledState(node.state)} />\n <span className=\"dt-node-title\">\n {node.title}\n <span className=\"dt-node-rel\">\n {relationLabel(node.relation)} · {node.evidenceCount} evidence\n </span>\n </span>\n <span className={`dt-state dt-state-${node.state}`}>{node.state}</span>\n </button>\n ))}\n <p className=\"dt-caption\">\n {confirmed} of {rows.length} confirmed by evidence.\n </p>\n </div>\n\n {/* ---- node detail ---- */}\n <div className=\"dt-pane dt-detail\">\n {detail === null ? (\n <p className=\"dt-empty\">\n {loading ? 'Loading…' : 'Pick a node on the map to see why it is there.'}\n </p>\n ) : (\n <>\n <p className=\"dt-eyebrow\">Selected node</p>\n <h2>{detail.node.title}</h2>\n <div className=\"dt-meta\">\n <StateMark state={detail.node.state} />\n <span className=\"dt-chip\">{relationLabel(detail.node.relation)}</span>\n {detail.parent !== null && <span className=\"dt-chip\">parent: {detail.parent.title}</span>}\n {detail.children.length > 0 && (\n <span className=\"dt-chip\">{detail.children.length} children</span>\n )}\n </div>\n\n <p className=\"dt-why\">{stateExplanation(detail.node.state)}</p>\n\n <p className=\"dt-section-title\">\n Evidence <span>{detail.node.evidence.length}</span>\n </p>\n {detail.node.evidence.length === 0 ? (\n <p className=\"dt-empty\">Nothing recorded yet.</p>\n ) : (\n <ul className=\"dt-evidence\">\n {detail.node.evidence.map((entry, index) => (\n <li key={index}>\n <div className=\"kind\">\n {entry.kind}\n {entry.readiness !== undefined && ` · ${entry.readiness}`}\n </div>\n {entry.note !== undefined && <div>{entry.note}</div>}\n <div className=\"when\">{entry.at}</div>\n </li>\n ))}\n </ul>\n )}\n\n {note !== null && <p className=\"dt-caption\">{note}</p>}\n {error !== null && <p className=\"dt-empty\">{error}</p>}\n <button type=\"button\" className=\"dt-primary\" onClick={state.start} disabled={starting}>\n {starting\n ? 'Starting…'\n : focus !== null && focus.nodeId === selectedId\n ? 'Learning in progress'\n : 'Start learning'}\n </button>\n </>\n )}\n </div>\n\n {/* ---- learning surface ---- */}\n <div className=\"dt-pane\">\n <div className=\"dt-lesson-head\">\n <p className=\"dt-eyebrow\">Learning surface</p>\n {lesson !== null && <span className=\"dt-origin\">{lesson.origin}</span>}\n </div>\n {lesson === null ? (\n focus !== null ? (\n <>\n <p className=\"dt-empty\">The tutor is preparing this node…</p>\n <p className=\"dt-caption\">\n The teaching appears here as it is written, while the conversation continues in the\n chat.\n </p>\n </>\n ) : (\n <p className=\"dt-empty\">\n Choose a node and press <b>Start learning</b>. The teaching appears here, and the\n conversation stays in the chat.\n </p>\n )\n ) : (\n <>\n <h2>{lesson.title}</h2>\n <div style={{ marginTop: 14 }}>\n <LessonBody blocks={lesson.blocks} />\n </div>\n </>\n )}\n {handoff !== null && lesson === null && focus !== null && (\n <HandoffLine handoff={handoff} onRetry={() => state.continueTo(handoff.targetNodeId)} />\n )}\n {nextStep !== null && (\n <NextStepCard\n nextStep={nextStep}\n busy={starting}\n onContinue={() => state.continueTo(nextStep.targetNodeId ?? nextStep.fromNodeId)}\n />\n )}\n {onOpenChat !== undefined && (\n <button type=\"button\" className=\"dt-primary\" onClick={onOpenChat}>\n Answer in the chat\n </button>\n )}\n {lesson !== null && (\n <button type=\"button\" className=\"dt-secondary\" onClick={state.dismissLesson}>\n Back to the node\n </button>\n )}\n {dataNote !== null && <p className=\"dt-caption\">{dataNote}</p>}\n <DataFooter onExport={state.exportData} onReset={state.resetAll} />\n </div>\n </div>\n )\n}\n","/**\n * The docked Learning tab.\n *\n * This is the surface that makes the loop usable. The full-page panel occupies\n * the main column, which is also where the conversation lives — so with only\n * that panel, answering a check means leaving the lesson. The right sidebar is\n * a *separate column*, so the same runtime sits beside the chat and the learner\n * never has to switch.\n *\n * Layout is deliberately narrow-first and ordered by what the learner needs\n * next: **the lesson, then the recommendation, then the map, then the\n * evidence.** The lesson leads because it is the teaching; the map and the\n * evidence are reference material the learner consults, not the main event.\n *\n * It runs on the same `useLearning` state as the full panel, so the two cannot\n * disagree about what is focused or what the tutor wrote.\n */\n\nimport { useEffect, useMemo, type ReactNode } from 'react'\n\nimport type { NodeView, OverviewResponse } from '../contract.js'\nimport { DataFooter, HandoffLine, LessonBody, NextStepCard } from './blocks.jsx'\nimport { buildMapTree, flattenTree, relationLabel, stateExplanation } from './model.js'\nimport type { PanelClient } from './use-learning.js'\nimport { defaultClient, useLearning } from './use-learning.js'\n\n/** The tab chip's text. */\nexport function LearningTabTitle(): ReactNode {\n return <>Learning</>\n}\n\nfunction CompactMap({\n nodes,\n selectedId,\n focusNodeId,\n onSelect,\n}: {\n nodes: NodeView[]\n selectedId: string | null\n focusNodeId: string | null\n onSelect: (id: string) => void\n}): ReactNode {\n const rows = useMemo(() => flattenTree(buildMapTree(nodes)), [nodes])\n const confirmed = nodes.filter((node) => node.state === 'confirmed').length\n\n return (\n <>\n <p className=\"dt-tab-section\">\n Diagnosis map <span>{nodes.length}</span>\n </p>\n {rows.length === 0 && <p className=\"dt-empty\">No map yet — state a goal in the chat.</p>}\n <div className=\"dt-tree\">\n {rows.map(({ node, depth }) => (\n <button\n key={node.id}\n type=\"button\"\n className=\"dt-tab-node\"\n style={{ paddingLeft: `${6 + depth * 14}px` }}\n // Depth as data, so the stylesheet can draw the guide without\n // measuring anything.\n data-depth={depth > 0 ? Math.min(depth, 3) : undefined}\n data-attention={node.state === 'blocked' || node.state === 'weak' ? 'true' : undefined}\n aria-current={node.id === selectedId}\n data-focused={node.id === focusNodeId}\n onClick={() => onSelect(node.id)}\n >\n <span className={`dt-dot dt-state-${node.state}`} data-filled={node.state === 'confirmed'} />\n <span className=\"dt-tab-node-title\">{node.title}</span>\n <span className={`dt-tab-state dt-state-${node.state}`}>{node.state}</span>\n </button>\n ))}\n </div>\n <p className=\"dt-caption\">\n {confirmed} of {nodes.length} confirmed by evidence.\n </p>\n </>\n )\n}\n\nexport interface LearningTabProps {\n /** Defaults to the real HTTP client. */\n client?: PanelClient\n /** Injected by the session scope; the tab asks the tutor in this session. */\n sessionId?: string | undefined\n /** Pre-supplied state, so the preview and tests skip the first fetch. */\n initialOverview?: OverviewResponse | undefined\n}\n\n/**\n * The docked learning surface.\n *\n * @param props - injectable client and the session the tab belongs to.\n * @returns the stacked node / map / lesson column.\n */\nexport function LearningTab({ client, sessionId, initialOverview }: LearningTabProps): ReactNode {\n const state = useLearning({ client: client ?? defaultClient, sessionId, initialOverview })\n const { overview, focus, nextStep, handoff, teachingBrain, dataNote, selectedId, detail, lesson, note, error, starting, loading, select } =\n state\n\n // Show something on first paint: the focused node if there is one, else the\n // map's first row. `select` is a stable callback, so this runs on arrival\n // rather than on every render.\n useEffect(() => {\n if (selectedId !== null) return\n const first = overview?.nodes?.[0]?.id\n if (first !== undefined) select(focus?.nodeId ?? first)\n }, [overview, focus, selectedId, select])\n\n const shownId = focus?.nodeId ?? selectedId\n\n const hasCourse = overview?.course != null\n const rows = overview?.nodes ?? []\n\n // First use: no goal yet. A blank panel would read as a broken panel, so it\n // says what to do and shows the exact sentence that starts everything. No\n // wizard — the chat is the input, and the shortest path there is one line of\n // text the learner can copy.\n if (!hasCourse) {\n return (\n <div className=\"dt-tab\">\n <div className=\"dt-welcome\">\n <p className=\"dt-welcome-eyebrow\">Universal Diagnostic Tutor</p>\n <h2 className=\"dt-welcome-title\">What do you want to learn?</h2>\n <p className=\"dt-welcome-body\">\n Works with the Universal Diagnostic Tutor skill. Start by telling the tutor what you\n want to learn — in your own words, in the chat.\n </p>\n <div className=\"dt-welcome-sample\">\n <span className=\"dt-welcome-sample-label\">Try</span>\n <span className=\"dt-welcome-sample-text\">\n I want to learn machine learning. I know some Python, but my math is weak.\n </span>\n </div>\n <p className=\"dt-welcome-note\">\n If tutoring doesn't start, make sure the UDT skill is available to your active DSH\n agent.\n </p>\n <p className=\"dt-welcome-note\">\n No account, no scores, no streak. Your goal, your map and the evidence behind it\n stay on this machine, and are yours to keep or delete.\n </p>\n <DataFooter onExport={state.exportData} onReset={state.resetAll} />\n {teachingBrain === false && (\n <p className=\"dt-notice\">\n <b>The Universal Diagnostic Tutor skill was not found.</b> This panel still records and\n shows your learning state, but no lesson will be written until the skill is available\n to your active DSH agent.\n </p>\n )}\n </div>\n </div>\n )\n }\n\n return (\n <div className=\"dt-tab\">\n {/* ---- now learning ---- */}\n {detail === null ? (\n // The header frame stays even before the first fetch lands, so the\n // surface never looks like an empty box on a slow connection.\n <header className=\"dt-now\">\n <p className=\"dt-now-eyebrow\">Now learning</p>\n <p className=\"dt-empty\">\n {loading ? 'Loading…' : 'Pick a node on the map below to begin.'}\n </p>\n </header>\n ) : (\n <header className=\"dt-now\" data-state={detail.node.state}>\n <p className=\"dt-now-eyebrow\">Now learning</p>\n <h2 className=\"dt-now-title\">{detail.node.title}</h2>\n <div className=\"dt-now-meta\">\n <span className={`dt-tab-state dt-state-${detail.node.state}`}>{detail.node.state}</span>\n <span className=\"dt-now-rel\">{relationLabel(detail.node.relation)}</span>\n {detail.parent !== null && <span className=\"dt-now-rel\">in {detail.parent.title}</span>}\n </div>\n <p className=\"dt-now-note\">{stateExplanation(detail.node.state)}</p>\n <button\n type=\"button\"\n className=\"dt-primary dt-now-action\"\n onClick={state.start}\n disabled={starting || shownId === null}\n >\n {starting\n ? 'Starting…'\n : focus !== null && focus.nodeId === shownId\n ? 'Keep going'\n : 'Start learning'}\n </button>\n {note !== null && <p className=\"dt-caption\">{note}</p>}\n {error !== null && <p className=\"dt-empty\">{error}</p>}\n </header>\n )}\n\n {/* ---- the teaching ---- */}\n {/* Always present, even before it has content: the surface names its three\n regions so a first-time learner can see what will fill them. */}\n <section className=\"dt-lesson\">\n <p className=\"dt-tab-section\">Learning surface</p>\n {handoff !== null && (\n <HandoffLine handoff={handoff} onRetry={() => state.continueTo(handoff.targetNodeId)} />\n )}\n {lesson === null ? (\n <p className=\"dt-empty\">\n {focus !== null\n ? 'The tutor is preparing this node…'\n : 'Press Start learning and the teaching appears here, while the chat stays open beside it.'}\n </p>\n ) : (\n <article className=\"dt-lesson-body\">\n <div className=\"dt-lesson-head\">\n <h3 className=\"dt-lesson-title\">{lesson.title}</h3>\n <span className=\"dt-origin\">{lesson.origin}</span>\n </div>\n <LessonBody blocks={lesson.blocks} />\n </article>\n )}\n </section>\n\n {/* ---- where to go next ---- */}\n {nextStep !== null && (\n <NextStepCard\n nextStep={nextStep}\n busy={starting}\n // Acting on the recommendation starts the focus it names — or, for a\n // stay, re-opens the same node so the tutor picks the thread back up.\n onContinue={() => {\n const target = nextStep.targetNodeId ?? nextStep.fromNodeId\n select(target)\n state.continueTo(target)\n }}\n />\n )}\n\n <CompactMap\n nodes={rows}\n selectedId={shownId}\n focusNodeId={focus?.nodeId ?? null}\n onSelect={select}\n />\n\n {dataNote !== null && <p className=\"dt-caption\">{dataNote}</p>}\n\n {/* ---- the evidence behind this node ---- */}\n {detail !== null && detail.node.evidence.length > 0 && (\n <>\n <p className=\"dt-tab-section\">\n Evidence <span>{detail.node.evidence.length}</span>\n </p>\n <ul className=\"dt-evidence\">\n {detail.node.evidence.map((entry, index) => (\n <li key={index}>\n <span className=\"dt-ev-kind\">{entry.kind}</span>\n {entry.readiness !== undefined && (\n <span className=\"dt-ev-readiness\">{entry.readiness.replace(/-/g, ' ')}</span>\n )}\n {entry.note !== undefined && <span className=\"dt-ev-note\">{entry.note}</span>}\n </li>\n ))}\n </ul>\n </>\n )}\n\n <DataFooter onExport={state.exportData} onReset={state.resetAll} />\n </div>\n )\n}\n","/**\n * The panel's stylesheet.\n *\n * Injected as a `<style data-plugin>` element rather than imported as CSS,\n * because styles never travel through the client bundler: the module table\n * serves one JavaScript file per plugin, so a separate CSS artifact has nowhere\n * to live. This is what the published UI plugins do too.\n *\n * The web styling guide forbids Tailwind and component libraries here, and\n * recommends the design system's semantic custom properties. Every one of those\n * tokens is used with a fallback, so the same stylesheet renders correctly in\n * the standalone preview where no DSH theme is present.\n */\n\n/** Marker attribute so the sheet is injected exactly once, and is findable. */\nexport const STYLE_ATTR = 'data-diagnostic-tutor-style'\n\nconst CSS = `.dt-root {\n --dt-bg: var(--dsw-alias-bg-base, #ffffff);\n --dt-surface: var(--dsw-alias-bg-elevated, #f7f8fa);\n --dt-sunken: var(--dsw-alias-bg-sunken, #f1f3f6);\n --dt-border: var(--dsw-alias-border-secondary, #e3e6ea);\n --dt-border-strong: var(--dsw-alias-border-primary, #cfd4dc);\n --dt-text: var(--dsw-alias-label-primary, #1c1f23);\n --dt-muted: var(--dsw-alias-label-secondary, #6b7280);\n --dt-accent: var(--dsw-alias-brand-primary, #4f46e5);\n --dt-good: #1f8b4c;\n --dt-warn: #b7791f;\n --dt-stop: #c0392b;\n --dt-radius: 10px;\n\n display: grid;\n grid-template-columns: minmax(260px, 300px) minmax(280px, 1fr) minmax(320px, 1.15fr);\n gap: 0;\n height: 100%;\n min-height: 0;\n background: var(--dt-bg);\n color: var(--dt-text);\n font-size: 13px;\n line-height: 1.6;\n text-align: left;\n}\n@media (max-width: 1080px) {\n .dt-root { grid-template-columns: 1fr; height: auto; }\n}\n\n.dt-pane { min-width: 0; min-height: 0; overflow: auto; padding: 16px 18px 40px; }\n.dt-pane + .dt-pane { border-left: 1px solid var(--dt-border); }\n@media (max-width: 1080px) {\n .dt-pane + .dt-pane { border-left: 0; border-top: 1px solid var(--dt-border); }\n}\n\n/* ---- shared type ------------------------------------------------------- */\n.dt-eyebrow,\n.dt-tab-section,\n.dt-now-eyebrow,\n.dt-welcome-eyebrow,\n.dt-block-label,\n.dt-next-label {\n font-size: 10px;\n letter-spacing: .1em;\n text-transform: uppercase;\n color: var(--dt-muted);\n margin: 0;\n}\n\n.dt-course-title { font-size: 17px; font-weight: 640; margin: 0 0 6px; letter-spacing: -.01em; }\n.dt-goal {\n margin: 0 0 16px; padding: 9px 11px; border-radius: var(--dt-radius);\n background: var(--dt-surface); border: 1px solid var(--dt-border);\n color: var(--dt-muted); font-size: 12px;\n}\n.dt-goal b { color: var(--dt-text); font-weight: 560; }\n\n.dt-section-title {\n display: flex; align-items: baseline; gap: 8px;\n font-size: 10px; letter-spacing: .1em; text-transform: uppercase;\n color: var(--dt-muted); margin: 22px 0 8px;\n}\n.dt-section-title span { text-transform: none; letter-spacing: 0; font-size: 11px; }\n\n.dt-caption { color: var(--dt-muted); font-size: 11px; margin: 6px 0 0; }\n.dt-empty { color: var(--dt-muted); font-size: 12.5px; margin: 8px 0; }\n.dt-cta-hint { margin-top: 14px; line-height: 1.6; }\n\n/* ---- first use --------------------------------------------------------- */\n/* The full-page variant has no columns to fill, so the question centres. */\n.dt-root-welcome { grid-template-columns: 1fr; place-items: center; }\n.dt-root-welcome .dt-welcome { max-width: 520px; padding: 8px 24px; }\n.dt-welcome { padding: 28px 4px 8px; }\n.dt-welcome-eyebrow { color: var(--dt-accent); }\n.dt-welcome-title {\n font-size: 21px; font-weight: 660; letter-spacing: -.015em;\n margin: 8px 0 10px; line-height: 1.25;\n}\n.dt-welcome-body { margin: 0 0 18px; color: var(--dt-muted); font-size: 13px; line-height: 1.65; }\n.dt-welcome-sample {\n border: 1px solid var(--dt-accent); border-radius: var(--dt-radius);\n background: var(--dt-surface); padding: 11px 13px;\n}\n.dt-welcome-sample-label {\n display: block; font-size: 10px; letter-spacing: .1em; text-transform: uppercase;\n color: var(--dt-accent); margin-bottom: 6px;\n}\n.dt-welcome-sample-text { font-size: 13px; line-height: 1.55; }\n.dt-welcome-note {\n margin: 18px 0 0; padding-top: 14px; border-top: 1px solid var(--dt-border);\n color: var(--dt-muted); font-size: 11.5px; line-height: 1.6;\n}\n\n/* Shown when nothing will teach: honest, actionable, and not a crash. */\n.dt-notice {\n margin: 16px 0 0; padding: 11px 13px; border-radius: var(--dt-radius);\n border: 1px solid var(--dt-warn);\n background: color-mix(in srgb, var(--dt-warn) 8%, var(--dt-bg));\n color: var(--dt-text); font-size: 12px; line-height: 1.6;\n}\n.dt-notice b { font-weight: 640; }\n\n/* ---- learner data controls --------------------------------------------- */\n/* Quiet by default: these are rights, not calls to action. They sit below the\n teaching so they are findable without competing with it. */\n.dt-data {\n margin-top: 26px; padding-top: 14px; border-top: 1px solid var(--dt-border);\n display: flex; flex-wrap: wrap; align-items: center; gap: 6px;\n}\n.dt-quiet {\n padding: 4px 10px; border-radius: 7px; border: 1px solid var(--dt-border);\n background: transparent; color: var(--dt-muted);\n font: inherit; font-size: 11.5px; cursor: pointer;\n}\n.dt-quiet:hover { background: var(--dt-surface); color: var(--dt-text); }\n.dt-quiet:disabled { opacity: .5; cursor: default; }\n.dt-data-note { flex-basis: 100%; color: var(--dt-muted); font-size: 11px; line-height: 1.5; }\n.dt-data-confirm {\n border-top-color: var(--dt-stop);\n background: color-mix(in srgb, var(--dt-stop) 6%, transparent);\n border-radius: var(--dt-radius); padding: 12px 13px; margin-top: 26px;\n}\n.dt-data-ask { margin: 0 0 10px; font-size: 12px; line-height: 1.6; }\n.dt-data-row { display: flex; gap: 8px; flex-wrap: wrap; }\n.dt-danger {\n padding: 5px 12px; border-radius: 7px; border: 1px solid var(--dt-stop);\n background: var(--dt-stop); color: #fff;\n font: inherit; font-size: 11.5px; font-weight: 550; cursor: pointer;\n}\n.dt-danger:disabled { opacity: .55; cursor: default; }\n\n/* ---- now learning ------------------------------------------------------ */\n.dt-now {\n padding: 13px 14px 14px;\n border: 1px solid var(--dt-border);\n border-radius: var(--dt-radius);\n background: var(--dt-surface);\n}\n/* The state is the headline fact about the node, so it colours the card edge. */\n.dt-now[data-state=\"blocked\"] { border-left: 3px solid var(--dt-stop); }\n.dt-now[data-state=\"weak\"] { border-left: 3px solid var(--dt-warn); }\n.dt-now[data-state=\"confirmed\"] { border-left: 3px solid var(--dt-good); }\n.dt-now[data-state=\"checked\"] { border-left: 3px solid #0f8f8f; }\n.dt-now[data-state=\"practiced\"] { border-left: 3px solid #5b53d6; }\n.dt-now[data-state=\"explained\"] { border-left: 3px solid #2f6feb; }\n.dt-now[data-state=\"unconfirmed\"] { border-left: 3px solid var(--dt-border-strong); }\n\n.dt-now-eyebrow { color: var(--dt-muted); }\n.dt-now-title {\n font-size: 17px; font-weight: 650; letter-spacing: -.015em;\n margin: 6px 0 8px; line-height: 1.28; overflow-wrap: anywhere;\n}\n.dt-now-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-bottom: 9px; }\n.dt-now-rel { color: var(--dt-muted); font-size: 11.5px; }\n.dt-now-note { margin: 0; color: var(--dt-muted); font-size: 12px; line-height: 1.6; }\n.dt-now-action { margin-top: 13px; width: 100%; }\n\n/* ---- diagnosis map ----------------------------------------------------- */\n.dt-tree { display: flex; flex-direction: column; gap: 1px; }\n.dt-tab-node {\n display: grid; grid-template-columns: auto 1fr auto;\n align-items: center; gap: 8px; width: 100%;\n padding: 6px 8px; margin: 0; border-radius: 7px;\n border: 1px solid transparent; border-left: 3px solid transparent;\n background: transparent; color: inherit; font: inherit;\n text-align: left; cursor: pointer; position: relative;\n}\n/* Depth as a drawn guide rather than only as indentation: a nested node should\n read as nested at a glance. */\n.dt-tab-node[data-depth]::before {\n content: ''; position: absolute; left: 4px; top: 0; bottom: 0;\n width: 1px; background: var(--dt-border);\n}\n.dt-tab-node[data-depth=\"2\"]::before { left: 18px; }\n.dt-tab-node[data-depth=\"3\"]::before { left: 32px; }\n.dt-tab-node:hover { background: var(--dt-surface); }\n.dt-tab-node[aria-current=\"true\"] {\n background: var(--dt-surface); border-color: var(--dt-border-strong);\n border-left-color: var(--dt-accent);\n}\n/* A blocker should be findable without reading every row. */\n.dt-tab-node[data-attention] { background: rgba(192, 57, 43, .05); }\n.dt-tab-node[data-attention]:hover { background: rgba(192, 57, 43, .09); }\n.dt-tab-node-title { min-width: 0; overflow-wrap: anywhere; line-height: 1.45; }\n.dt-tab-node[data-depth=\"0\"] > .dt-tab-node-title { font-weight: 600; }\n\n.dt-dot {\n width: 8px; height: 8px; border-radius: 50%;\n border: 1.5px solid currentColor; background: transparent; flex: none;\n}\n.dt-dot[data-filled=\"true\"] { background: currentColor; }\n\n.dt-state-unconfirmed { color: #8b93a1; }\n.dt-state-explained { color: #2f6feb; }\n.dt-state-practiced { color: #5b53d6; }\n.dt-state-checked { color: #0f8f8f; }\n.dt-state-weak { color: var(--dt-warn); }\n.dt-state-blocked { color: var(--dt-stop); }\n.dt-state-confirmed { color: var(--dt-good); }\n\n/* A tinted chip scans far better than coloured words alone. */\n.dt-tab-state {\n font-size: 10.5px; font-weight: 560; letter-spacing: .01em;\n padding: 1px 7px; border-radius: 999px;\n border: 1px solid currentColor; white-space: nowrap;\n background: color-mix(in srgb, currentColor 10%, transparent);\n}\n.dt-state { font-style: normal; }\n\n/* ---- node detail (full panel) ------------------------------------------ */\n.dt-detail h2 { font-size: 16px; margin: 0 0 8px; font-weight: 650; letter-spacing: -.01em; }\n.dt-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-bottom: 12px; }\n.dt-chip {\n font-size: 11px; color: var(--dt-muted);\n border: 1px solid var(--dt-border); border-radius: 999px; padding: 1px 8px;\n}\n.dt-why { margin: 0 0 14px; color: var(--dt-muted); font-size: 12.5px; line-height: 1.6; }\n.dt-node {\n display: grid; grid-template-columns: auto 1fr auto;\n align-items: center; gap: 8px; width: 100%;\n padding: 6px 8px; margin: 1px 0; border-radius: 7px;\n border: 1px solid transparent; background: transparent;\n color: inherit; font: inherit; text-align: left; cursor: pointer;\n}\n.dt-node:hover { background: var(--dt-surface); }\n.dt-node[aria-current=\"true\"] { background: var(--dt-surface); border-color: var(--dt-border-strong); }\n.dt-node-title { min-width: 0; overflow-wrap: anywhere; }\n.dt-node-rel { display: block; color: var(--dt-muted); font-size: 11px; font-weight: 400; }\n\n/* ---- evidence ---------------------------------------------------------- */\n/* Compact on purpose: this is a record, not a reading surface. One row per\n entry, with the note clamped so a long one cannot bury the next. */\n.dt-evidence { list-style: none; margin: 0; padding: 0; }\n.dt-evidence li {\n display: grid; grid-template-columns: auto 1fr; gap: 2px 8px;\n padding: 7px 0 7px 10px; border-left: 2px solid var(--dt-border);\n font-size: 11.5px; line-height: 1.5;\n}\n.dt-evidence li + li { margin-top: 2px; }\n.dt-ev-kind {\n font-weight: 600; font-size: 11px; color: var(--dt-text);\n text-transform: capitalize;\n}\n.dt-ev-readiness {\n font-size: 10.5px; color: var(--dt-muted);\n border: 1px solid var(--dt-border); border-radius: 999px; padding: 0 6px;\n justify-self: start;\n}\n.dt-ev-note {\n grid-column: 1 / -1; color: var(--dt-muted);\n display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n/* ---- buttons ----------------------------------------------------------- */\n.dt-primary {\n padding: 7px 14px; border-radius: 8px; border: 1px solid transparent;\n background: var(--dt-accent); color: #fff;\n font: inherit; font-weight: 550; cursor: pointer;\n}\n.dt-primary:hover { filter: brightness(1.07); }\n.dt-primary:disabled { opacity: .5; cursor: default; filter: none; }\n.dt-secondary {\n margin-top: 8px; padding: 6px 12px; border-radius: 8px;\n border: 1px solid var(--dt-border); background: transparent;\n color: inherit; font: inherit; font-size: 12px; cursor: pointer;\n}\n.dt-secondary:hover { background: var(--dt-surface); }\n\n/* ---- lesson ------------------------------------------------------------ */\n.dt-lesson { margin-top: 20px; }\n.dt-lesson-body {\n border: 1px solid var(--dt-border); border-radius: var(--dt-radius);\n background: var(--dt-bg); padding: 16px 16px 6px;\n}\n.dt-lesson-head {\n display: flex; align-items: baseline; justify-content: space-between; gap: 8px;\n padding-bottom: 10px; margin-bottom: 14px;\n border-bottom: 1px solid var(--dt-border);\n}\n.dt-lesson-title { font-size: 15px; font-weight: 650; margin: 0; letter-spacing: -.01em; line-height: 1.35; }\n.dt-tab-lesson-title { font-weight: 600; }\n.dt-origin {\n flex: none; font-size: 9.5px; letter-spacing: .08em; text-transform: uppercase;\n color: var(--dt-muted); border: 1px solid var(--dt-border);\n border-radius: 999px; padding: 1px 7px;\n}\n\n/* ---- blocks ------------------------------------------------------------ */\n.dt-block { margin: 0 0 18px; }\n/* A readable measure: full-width prose in a docked panel is tiring. */\n.dt-block-md { margin: 0 0 11px; line-height: 1.66; max-width: 62ch; }\n.dt-block-md:last-child { margin-bottom: 0; }\n.dt-block-md strong { font-weight: 640; }\n\n/* Headings the teaching brain writes inline as ### become real headings. */\n.dt-md-h2 {\n font-size: 15px; font-weight: 650; letter-spacing: -.01em;\n margin: 20px 0 9px; line-height: 1.35;\n}\n.dt-md-h2:first-child, .dt-md-h3:first-child { margin-top: 0; }\n.dt-md-h3 {\n font-size: 13.5px; font-weight: 650; color: var(--dt-text);\n margin: 18px 0 7px; line-height: 1.4;\n}\n.dt-md-ul, .dt-md-ol { margin: 0 0 12px; padding-left: 20px; max-width: 62ch; }\n.dt-md-ul li, .dt-md-ol li { margin-bottom: 5px; line-height: 1.6; }\n.dt-md-ul { list-style: none; padding-left: 4px; }\n.dt-md-ul li { position: relative; padding-left: 16px; }\n.dt-md-ul li::before {\n content: ''; position: absolute; left: 3px; top: .62em;\n width: 4px; height: 4px; border-radius: 50%; background: var(--dt-border-strong);\n}\n.dt-md-ol { list-style: decimal; }\n.dt-md-ol li::marker { color: var(--dt-muted); font-variant-numeric: tabular-nums; }\n\n.dt-block-label {\n color: var(--dt-accent); margin-bottom: 7px;\n}\n\n/* Worked example: a distinct panel, because it is a different kind of reading\n from the prose around it. */\n.dt-block-example {\n border: 1px solid var(--dt-border); border-left: 3px solid var(--dt-border-strong);\n border-radius: var(--dt-radius); background: var(--dt-surface);\n padding: 12px 14px 13px;\n}\n.dt-block-example h4 { margin: 0 0 9px; font-size: 13px; font-weight: 620; line-height: 1.4; }\n.dt-block-example ol { margin: 0; padding-left: 19px; max-width: 62ch; }\n.dt-block-example li { margin-bottom: 6px; line-height: 1.6; }\n.dt-block-example li::marker { color: var(--dt-muted); font-variant-numeric: tabular-nums; }\n.dt-takeaway {\n margin: 11px 0 0; padding-top: 9px; border-top: 1px dashed var(--dt-border);\n font-size: 12px; color: var(--dt-text);\n}\n\n/* Diagram: monospace deserves its own frame so it reads as a figure. */\n.dt-block-diagram { padding: 0; }\n.dt-pre {\n margin: 0; padding: 12px 14px; overflow-x: auto;\n background: var(--dt-sunken); border: 1px solid var(--dt-border);\n border-radius: var(--dt-radius);\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 11.5px; line-height: 1.6; white-space: pre;\n}\n\n/* Check: this is the moment the lesson hands over to the learner, so it gets\n the strongest treatment on the surface. */\n.dt-block-check {\n border: 1px solid var(--dt-accent); border-radius: var(--dt-radius);\n background: color-mix(in srgb, var(--dt-accent) 5%, var(--dt-bg));\n padding: 0; overflow: hidden;\n}\n.dt-check-head {\n display: flex; align-items: center; justify-content: space-between; gap: 8px;\n padding: 9px 14px; background: var(--dt-accent); color: #fff;\n}\n.dt-check-tag {\n font-size: 10px; letter-spacing: .12em; text-transform: uppercase; font-weight: 650;\n}\n.dt-check-where { font-size: 11px; opacity: .9; }\n.dt-check-body { padding: 13px 14px 4px; }\n.dt-check-body .dt-block-md:last-child { margin-bottom: 11px; }\n.dt-check-hint {\n margin: 0; padding: 9px 14px 11px; border-top: 1px dashed var(--dt-border);\n font-size: 11.5px; color: var(--dt-muted);\n}\n.dt-check-hint span {\n font-size: 10px; letter-spacing: .1em; text-transform: uppercase;\n margin-right: 7px; color: var(--dt-accent);\n}\n\n/* ---- math (styled, not typeset) ---------------------------------------- */\n.dt-math {\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: .95em; padding: 0 3px; border-radius: 4px;\n background: var(--dt-sunken); white-space: nowrap;\n}\n.dt-math-block {\n margin: 0 0 12px; padding: 11px 13px; overflow-x: auto;\n background: var(--dt-sunken); border: 1px solid var(--dt-border);\n border-radius: var(--dt-radius);\n font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 12px; white-space: pre-wrap;\n}\n\n/* ---- handoff progress -------------------------------------------------- */\n.dt-handoff {\n display: flex; align-items: center; gap: 8px; flex-wrap: wrap;\n margin: 0 0 12px; padding: 8px 11px; border-radius: 8px;\n background: var(--dt-surface); border: 1px solid var(--dt-border);\n font-size: 12px;\n}\n.dt-handoff-dot {\n width: 7px; height: 7px; border-radius: 50%; background: var(--dt-accent);\n animation: dt-pulse 1.4s ease-in-out infinite;\n}\n.dt-handoff[data-phase=\"lesson-ready\"] {\n border-color: color-mix(in srgb, var(--dt-good) 45%, var(--dt-border));\n background: color-mix(in srgb, var(--dt-good) 7%, var(--dt-bg));\n}\n.dt-handoff[data-phase=\"lesson-ready\"] .dt-handoff-dot { background: var(--dt-good); animation: none; }\n.dt-handoff[data-phase=\"failed\"] .dt-handoff-dot,\n.dt-handoff[data-phase=\"stalled\"] .dt-handoff-dot { background: var(--dt-stop); animation: none; }\n@keyframes dt-pulse { 0%, 100% { opacity: 1 } 50% { opacity: .3 } }\n.dt-handoff-label { font-weight: 550; }\n.dt-handoff-time, .dt-handoff-try, .dt-handoff-detail { color: var(--dt-muted); font-size: 11px; }\n.dt-handoff-detail { flex-basis: 100%; }\n.dt-handoff-retry {\n margin-left: auto; padding: 3px 10px; border-radius: 6px;\n border: 1px solid var(--dt-border); background: transparent;\n color: inherit; font: inherit; font-size: 11px; cursor: pointer;\n}\n.dt-handoff-retry:hover { background: var(--dt-bg); }\n@media (prefers-reduced-motion: reduce) { .dt-handoff-dot { animation: none } }\n\n/* ---- next best step ---------------------------------------------------- */\n/* The conclusion of a node. It should be the most deliberate thing on the\n surface: what just finished, where it sends you, why, and one button. */\n.dt-next {\n margin: 20px 0 4px; padding: 14px 15px 15px; border-radius: var(--dt-radius);\n border: 1px solid var(--dt-accent);\n background: color-mix(in srgb, var(--dt-accent) 6%, var(--dt-bg));\n box-shadow: 0 1px 2px rgba(0, 0, 0, .04);\n}\n.dt-next-label {\n display: flex; align-items: center; gap: 6px;\n color: var(--dt-accent); font-weight: 650; margin-bottom: 10px;\n}\n.dt-next-arrow { font-size: 12px; }\n.dt-next-from { margin: 0 0 8px; color: var(--dt-muted); font-size: 11.5px; }\n.dt-next-tick { color: var(--dt-good); margin-right: 5px; }\n.dt-next-target {\n margin: 0 0 10px; font-size: 14.5px; line-height: 1.4; letter-spacing: -.01em;\n}\n.dt-next-target b { font-weight: 660; }\n.dt-next-why { margin: 0; font-size: 12px; line-height: 1.6; color: var(--dt-text); }\n.dt-next-why-label { color: var(--dt-muted); }\n.dt-next .dt-primary { margin-top: 14px; width: 100%; }\n\n/* ---- docked tab -------------------------------------------------------- */\n.dt-tab { padding: 14px 14px 40px; font-size: 12.5px; color: var(--dt-text); text-align: left; }\n.dt-tab-head { display: flex; align-items: baseline; gap: 8px; justify-content: space-between; }\n.dt-tab-title { font-size: 15px; font-weight: 650; margin: 0; }\n.dt-tab-meta { color: var(--dt-muted); font-size: 11px; margin: 4px 0 8px; }\n.dt-tab-action { margin-top: 10px; }\n.dt-tab-section {\n display: flex; align-items: baseline; gap: 8px;\n margin: 24px 0 8px; padding-top: 14px;\n border-top: 1px solid var(--dt-border);\n}\n.dt-tab-section span { text-transform: none; letter-spacing: 0; font-size: 11px; }\n\n.dt-block-unknown {\n border: 1px dashed var(--dt-border); border-radius: 8px;\n padding: 10px 12px; color: var(--dt-muted);\n}\n\n`\n\n/**\n * Inject the stylesheet once.\n *\n * @returns a disposer that removes the element, so an unload leaves no residue.\n */\nexport function injectStyles(): () => void {\n const existing = document.querySelector(`style[${STYLE_ATTR}]`)\n if (existing) return () => {}\n\n const element = document.createElement('style')\n element.setAttribute(STYLE_ATTR, '')\n element.textContent = CSS\n document.head.appendChild(element)\n return () => element.remove()\n}\n","/**\n * dsh-diagnostic-tutor — browser half.\n *\n * Registers two things, which together are the whole navigation story:\n *\n * - an icon in the left sidebar (`sidebar.panellist`, a `list`), whose `id`\n * addresses the matching main panel;\n * - the panel itself (`main`, a `keyed` slot) under that same id.\n *\n * The framework supplies the button chrome and the panel switching; this module\n * only says what the icon looks like and what the panel renders. No DSH\n * component is re-implemented and no DSH internal is reached into.\n *\n * Both registrations go through `ctx.slots.inject(...)`, which is mandatory\n * rather than stylistic: registering into a slot before its owner has declared\n * it throws.\n */\n\nimport type { Context } from '@deepseek-ai/cordis'\nimport type { PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'\n// Type-only side-effect imports. `ctx.slots` is declared by ui-renderer, and\n// each UI package contributes its own `SlotMap` rows through its `/client`\n// subpath — so these are what make `ctx.slots` and the slot keys typecheck.\nimport type {} from '@deepseek-ai/dsh-client-ui-renderer/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-layout/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-sidebar/client'\nimport type {} from '@deepseek-ai/dsh-client-ui-sidebar-right/client'\nimport type { ReactNode } from 'react'\n\nimport { LearningPanel } from './app.js'\nimport { fetchOverview } from './api.js'\nimport { LearningTab, LearningTabTitle } from './tab.jsx'\nimport { injectStyles } from './styles.js'\n\n/** Client module name; distinct from the host plugin's name. */\nexport const name = 'diagnostic-tutor-client'\n\n/**\n * `slots` is the only service needed.\n *\n * Declared here rather than in the host half because it is a *client* service:\n * the browser half runs in its own Cordis context against the page's registry.\n */\nexport const inject = ['slots', 'layout', 'sidebarRightTabs']\n\n/**\n * Identifier shared by the sidebar entry and the main panel.\n *\n * These two must match — the sidebar's list `id` is what the layout uses to\n * select the `main` key — so they are derived from one constant.\n */\nexport const PANEL_ID = 'diagnostic-tutor'\n\n/**\n * The docked tab's identity.\n *\n * A tab type has two names: `id` is the implementation's own identity and is\n * what its body and title register under; `kind` is the discriminator `openTab`\n * names. They are the same string here because this plugin ships exactly one\n * implementation of exactly one kind.\n */\nexport const TAB_ID = 'diagnostic-tutor'\n\n/** The sidebar icon: a small node graph, drawn inline so it needs no assets. */\nfunction PanelIcon({ size, active }: PropsRuntime<'sidebar.panellist'>): ReactNode {\n return (\n <svg\n width={size}\n height={size}\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={active ? 1.6 : 1.3}\n aria-hidden=\"true\"\n >\n <circle cx=\"8\" cy=\"3\" r=\"2\" />\n <circle cx=\"3.5\" cy=\"12\" r=\"2\" />\n <circle cx=\"12.5\" cy=\"12\" r=\"2\" />\n <path d=\"M8 5v2.2M8 7.2 4.6 10.3M8 7.2l3.4 3.1\" strokeLinecap=\"round\" />\n </svg>\n )\n}\n\n/**\n * The panel body, with the session the tutor should be woken in threaded\n * through, plus a way back to the conversation.\n *\n * That last part matters more than it looks. The map, the node detail and the\n * learning surface occupy the **main** column, which is the same column the\n * conversation lives in — so while the panel is open the learner cannot type.\n * Checks are answered in the chat, so the surface needs a door back to it, and\n * `ctx.layout.selectPanel(null)` is the documented way to show the conversation\n * again.\n *\n * `useSessions` is part of the standard kit every root-scoped slot receives, so\n * the panel can name the session it is looking at without any plumbing of its\n * own. The host needs it to reach the right agent when the learner presses\n * Start learning.\n */\nfunction MainPanel({\n useSessions,\n onOpenChat,\n onFocusStarted,\n}: {\n useSessions?: ((select: (state: SessionListLike) => unknown) => unknown) | undefined\n onOpenChat?: (() => void) | undefined\n onFocusStarted?: (() => void) | undefined\n}): ReactNode {\n const sessionId = useSessions?.((state) => state.ids[0])\n return (\n <LearningPanel\n {...(typeof sessionId === 'string' ? { sessionId } : {})}\n {...(onOpenChat === undefined ? {} : { onOpenChat })}\n {...(onFocusStarted === undefined ? {} : { onFocusStarted })}\n />\n )\n}\n\n/** The slice of the session-list snapshot this panel reads. */\ninterface SessionListLike {\n ids: string[]\n}\n\n/** Props the slot supplies; re-declared for the bound wrapper. */\ninterface MainPanelProps {\n useSessions?: ((select: (state: SessionListLike) => unknown) => unknown) | undefined\n}\n\n/**\n * Load the browser half.\n *\n * @param ctx - the client Cordis context.\n */\nexport function apply(ctx: Context): void {\n // The sheet is injected once and removed with the plugin.\n ctx.effect(() => injectStyles())\n\n ctx.slots.inject('sidebar.panellist', () =>\n ctx.slots.register(\n {\n name: 'sidebar.panellist',\n id: PANEL_ID,\n order: 40,\n label: 'Learn',\n },\n PanelIcon,\n ),\n )\n\n // Captured once: the panel needs *actions*, and the standard props a slot\n // receives only expose reads.\n const layout = ctx.get('layout')\n\n ctx.slots.inject('main', () =>\n ctx.slots.register(\n {\n name: 'main',\n key: PANEL_ID,\n },\n function BoundMainPanel(props: MainPanelProps): ReactNode {\n return (\n <MainPanel\n {...props}\n onOpenChat={() => {\n // Show the conversation, then dock: the session surface that the\n // right sidebar needs comes back with it.\n layout?.selectPanel(null)\n openDockedTab()\n }}\n onFocusStarted={() => openDockedTab()}\n />\n )\n },\n ),\n )\n\n // ---------------------------------------------------------------------------\n // The docked tab.\n //\n // This is what lets the chat and the learning surface coexist. The full panel\n // takes the main column — the same column the conversation lives in — so with\n // only that panel the learner must leave the lesson to answer a check. The\n // right sidebar is a separate column, so the same runtime sits beside the\n // chat.\n //\n // Registration is two stages, and the order is enforced by the framework:\n // declare the tab type, then register the body and title under the type's own\n // `id`, because that is the key the seats dispatch on.\n // ---------------------------------------------------------------------------\n ctx.sidebarRightTabs.register({\n id: TAB_ID,\n kind: TAB_ID,\n // A page type, not a resource viewer: it has no address globs, so it is\n // opened by kind and never competes to render someone else's resource.\n title: () => 'Learning',\n priority: 'extension',\n })\n\n /**\n * Dock the tab, retrying briefly.\n *\n * The right sidebar hosts **session-scoped** tabs, so it can only accept one\n * while a session surface is mounted — and while the full panel occupies the\n * main column, no session surface exists. Returning to the conversation\n * mounts one, so the useful moment to dock is just after that. Retrying\n * covers the gap between the click and the surface mounting.\n */\n const openDockedTab = (attempt = 0): void => {\n // Resolved with `get`, not read as a property: this plugin injects the\n // *registry*, not the navigation controller, and reading a service that was\n // never injected throws rather than returning undefined.\n const sidebarRight = ctx.get('sidebarRight')\n if (!sidebarRight) return\n try {\n sidebarRight.openTab(TAB_ID)\n } catch {\n if (attempt < 8) setTimeout(() => openDockedTab(attempt + 1), 400)\n }\n }\n\n ctx.slots.inject('sidebar.right.pane.tab', () =>\n ctx.slots.register({ name: 'sidebar.right.pane.tab', key: TAB_ID }, LearningTab),\n )\n ctx.slots.inject('sidebar.right.pane.tab.title', () =>\n ctx.slots.register({ name: 'sidebar.right.pane.tab.title', key: TAB_ID }, LearningTabTitle),\n )\n\n // Resume: if a focus is already active — a session being picked back up —\n // have the surface docked and waiting rather than hidden behind a click.\n void fetchOverview()\n .then((overview) => {\n if (overview.course !== null && overview.focus !== null) openDockedTab()\n })\n .catch(() => {\n // The API may not be mounted (a client-only page); nothing to open.\n })\n}\n\nexport { LearningPanel } from './app.js'\nexport { LearningTab, LearningTabTitle } from './tab.jsx'\nexport { LessonBody, BlockView, BLOCK_RENDERERS } from './blocks.js'\nexport type { PanelClient } from './app.js'\n"],"mappings":"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"}
|
package/lib/contract.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The host↔browser contract.
|
|
3
|
+
*
|
|
4
|
+
* Types only — this module has **no runtime imports at all**, so the client
|
|
5
|
+
* bundle can pull from it freely without dragging host code (or zod) into the
|
|
6
|
+
* browser. That separation is deliberate: the data schema lives in
|
|
7
|
+
* `lesson.ts` and `state.ts`, the renderers live in `client/`, and this file is
|
|
8
|
+
* the shape they agree on.
|
|
9
|
+
*
|
|
10
|
+
* These are **views**, not records. The browser never receives a storage
|
|
11
|
+
* record, a domain handle or a path; it receives exactly what the panel draws.
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=contract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG"}
|
package/lib/diagnosis.js
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Diagnosis-map rules.
|
|
3
|
+
*
|
|
4
|
+
* Everything here is a pure function over records: no IO, no `ctx`, no clock.
|
|
5
|
+
* That is deliberate — this is the module that decides what the runtime is
|
|
6
|
+
* *allowed* to store, so it must be trivially testable and impossible to
|
|
7
|
+
* bypass from the tool layer.
|
|
8
|
+
*
|
|
9
|
+
* The rules exist to make two failures structurally impossible:
|
|
10
|
+
*
|
|
11
|
+
* 1. **Unverified mastery.** `confirmed` is unreachable without evidence the
|
|
12
|
+
* skill itself considers strong enough. A model cannot simply assert it.
|
|
13
|
+
* 2. **A curriculum in disguise.** Nodes must attach to a node that already
|
|
14
|
+
* exists, batches are small, and a course is capped. There is no call that
|
|
15
|
+
* plants a finished syllabus.
|
|
16
|
+
*
|
|
17
|
+
* These are floors, not pedagogy. Clearing them makes a state *permissible*;
|
|
18
|
+
* whether it is *right* remains the skill's judgement.
|
|
19
|
+
*/
|
|
20
|
+
import { INITIAL_NODE_STATE, isConfirmingEvidence, } from './vocabulary.js';
|
|
21
|
+
/* -------------------------------------------------------------------------- */
|
|
22
|
+
/* Limits */
|
|
23
|
+
/* -------------------------------------------------------------------------- */
|
|
24
|
+
/**
|
|
25
|
+
* Most nodes one `udt_map_update` call may add.
|
|
26
|
+
*
|
|
27
|
+
* A diagnosis reveals a small cluster near the blocker, not a term's worth of
|
|
28
|
+
* material. The skill's own guidance for a broad goal is "the first one to
|
|
29
|
+
* three nodes"; eight leaves room for a genuine cluster while making a
|
|
30
|
+
* thirty-node dump impossible in one call.
|
|
31
|
+
*/
|
|
32
|
+
export const MAX_NODES_PER_UPDATE = 8;
|
|
33
|
+
/** Most nodes one course may accumulate. A map is a diagnosis aid, not a textbook. */
|
|
34
|
+
export const MAX_NODES_PER_COURSE = 40;
|
|
35
|
+
function violation(code, message, nodeId) {
|
|
36
|
+
return nodeId === undefined ? { code, message } : { code, message, nodeId };
|
|
37
|
+
}
|
|
38
|
+
/* -------------------------------------------------------------------------- */
|
|
39
|
+
/* Single-node rules */
|
|
40
|
+
/* -------------------------------------------------------------------------- */
|
|
41
|
+
/**
|
|
42
|
+
* Whether this node's state is permitted by its evidence.
|
|
43
|
+
*
|
|
44
|
+
* @param node - the record to check.
|
|
45
|
+
* @returns a violation, or `undefined` when the state is permissible.
|
|
46
|
+
*/
|
|
47
|
+
export function checkNodeState(node) {
|
|
48
|
+
if (node.state === 'confirmed') {
|
|
49
|
+
// The goal is the frame the learner is working inside, not a claim about
|
|
50
|
+
// what they can do; there is nothing to confirm about it.
|
|
51
|
+
if (node.relation === 'goal') {
|
|
52
|
+
return violation('goal-node-confirmed', 'A goal node is the frame of the map, not a mastery claim, so it cannot be "confirmed".', node.id);
|
|
53
|
+
}
|
|
54
|
+
const confirming = node.evidence.filter((entry) => isConfirmingEvidence(entry.kind));
|
|
55
|
+
if (confirming.length === 0) {
|
|
56
|
+
return violation('confirmed-without-evidence', 'A node cannot be "confirmed" without at least one check or transfer evidence entry. ' +
|
|
57
|
+
'Explanation or practice alone never confirms.', node.id);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
/* -------------------------------------------------------------------------- */
|
|
63
|
+
/* Map-level rules */
|
|
64
|
+
/* -------------------------------------------------------------------------- */
|
|
65
|
+
/**
|
|
66
|
+
* Validate one course's map as a whole.
|
|
67
|
+
*
|
|
68
|
+
* @param nodes - the nodes that would exist after the proposed change.
|
|
69
|
+
* @returns every violation found, in node order.
|
|
70
|
+
*/
|
|
71
|
+
export function validateMap(nodes) {
|
|
72
|
+
const violations = [];
|
|
73
|
+
const byId = new Map();
|
|
74
|
+
for (const node of nodes) {
|
|
75
|
+
if (byId.has(node.id)) {
|
|
76
|
+
violations.push(violation('duplicate-node-id', `Duplicate node id "${node.id}".`, node.id));
|
|
77
|
+
}
|
|
78
|
+
byId.set(node.id, node);
|
|
79
|
+
const stateViolation = checkNodeState(node);
|
|
80
|
+
if (stateViolation)
|
|
81
|
+
violations.push(stateViolation);
|
|
82
|
+
}
|
|
83
|
+
const goals = nodes.filter((node) => node.relation === 'goal');
|
|
84
|
+
for (const goal of goals) {
|
|
85
|
+
if (goal.parentId !== undefined) {
|
|
86
|
+
violations.push(violation('goal-node-has-parent', 'A goal node is the root of its course and has no parent.', goal.id));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// Scoped per course: several courses coexist in one domain.
|
|
90
|
+
const goalCountByCourse = new Map();
|
|
91
|
+
for (const goal of goals) {
|
|
92
|
+
goalCountByCourse.set(goal.courseId, (goalCountByCourse.get(goal.courseId) ?? 0) + 1);
|
|
93
|
+
}
|
|
94
|
+
for (const [courseId, count] of goalCountByCourse) {
|
|
95
|
+
if (count > 1) {
|
|
96
|
+
violations.push(violation('multiple-goal-nodes', `Course "${courseId}" has ${count} goal nodes; exactly one is allowed.`));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
for (const node of nodes) {
|
|
100
|
+
if (node.relation === 'goal')
|
|
101
|
+
continue;
|
|
102
|
+
if (node.parentId === undefined) {
|
|
103
|
+
violations.push(violation('goal-node-not-root', 'Every node other than a course goal must attach to an existing node via parentId.', node.id));
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (node.parentId === node.id) {
|
|
107
|
+
violations.push(violation('self-parent', 'A node cannot be its own parent.', node.id));
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const parent = byId.get(node.parentId);
|
|
111
|
+
if (!parent) {
|
|
112
|
+
violations.push(violation('missing-parent', `Parent node "${node.parentId}" does not exist in this map.`, node.id));
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (parent.courseId !== node.courseId) {
|
|
116
|
+
violations.push(violation('parent-in-other-course', `Parent "${parent.id}" belongs to another course.`, node.id));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// Cycle detection over the parent chain, bounded by the node count.
|
|
120
|
+
for (const node of nodes) {
|
|
121
|
+
const seen = new Set([node.id]);
|
|
122
|
+
let cursor = node.parentId;
|
|
123
|
+
while (cursor !== undefined) {
|
|
124
|
+
if (seen.has(cursor)) {
|
|
125
|
+
violations.push(violation('parent-cycle', 'The parent chain forms a cycle.', node.id));
|
|
126
|
+
break;
|
|
127
|
+
}
|
|
128
|
+
seen.add(cursor);
|
|
129
|
+
cursor = byId.get(cursor)?.parentId;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return violations;
|
|
133
|
+
}
|
|
134
|
+
/* -------------------------------------------------------------------------- */
|
|
135
|
+
/* Operations */
|
|
136
|
+
/* -------------------------------------------------------------------------- */
|
|
137
|
+
/**
|
|
138
|
+
* Build the record for a proposed new node and check it against the map.
|
|
139
|
+
*
|
|
140
|
+
* @param existing - the course's current nodes.
|
|
141
|
+
* @param proposed - the node as supplied, already shaped by `newNode`.
|
|
142
|
+
* @returns either the accepted record or the violations that rejected it.
|
|
143
|
+
*/
|
|
144
|
+
export function acceptNewNodes(existing, proposed) {
|
|
145
|
+
const violations = [];
|
|
146
|
+
if (proposed.length === 0) {
|
|
147
|
+
violations.push(violation('batch-too-large', 'No nodes were supplied.'));
|
|
148
|
+
}
|
|
149
|
+
if (proposed.length > MAX_NODES_PER_UPDATE) {
|
|
150
|
+
violations.push(violation('batch-too-large', `A single update may add at most ${MAX_NODES_PER_UPDATE} nodes, received ${proposed.length}. ` +
|
|
151
|
+
'A diagnosis reveals a small cluster, not a whole course.'));
|
|
152
|
+
}
|
|
153
|
+
if (existing.length + proposed.length > MAX_NODES_PER_COURSE) {
|
|
154
|
+
violations.push(violation('course-too-large', `A course may hold at most ${MAX_NODES_PER_COURSE} nodes (${existing.length} + ${proposed.length}).`));
|
|
155
|
+
}
|
|
156
|
+
const existingIds = new Set(existing.map((node) => node.id));
|
|
157
|
+
for (const node of proposed) {
|
|
158
|
+
if (existingIds.has(node.id)) {
|
|
159
|
+
violations.push(violation('duplicate-node-id', `Node "${node.id}" already exists.`, node.id));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (violations.length > 0)
|
|
163
|
+
return { ok: false, violations };
|
|
164
|
+
// Validate the resulting map as a whole, so parent lookups and cycles are
|
|
165
|
+
// checked against what would actually exist after the write.
|
|
166
|
+
const next = [...existing, ...proposed];
|
|
167
|
+
const mapViolations = validateMap(next);
|
|
168
|
+
if (mapViolations.length > 0)
|
|
169
|
+
return { ok: false, violations: mapViolations };
|
|
170
|
+
return { ok: true, nodes: next };
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Move a node to a new state without recording new evidence.
|
|
174
|
+
*
|
|
175
|
+
* Used when the *reading* of existing evidence changes — the skill re-grades
|
|
176
|
+
* what it already saw. The resulting record is validated by the same rule as
|
|
177
|
+
* everything else, so this is not a bypass: a bare assertion still cannot
|
|
178
|
+
* promote a node to `confirmed`.
|
|
179
|
+
*
|
|
180
|
+
* @param node - the node being updated.
|
|
181
|
+
* @param next - the proposed state.
|
|
182
|
+
* @param now - ISO timestamp for `updatedAt`.
|
|
183
|
+
* @returns either the updated record or the violation that rejected it.
|
|
184
|
+
*/
|
|
185
|
+
export function setNodeState(node, next, now) {
|
|
186
|
+
const candidate = { ...node, state: next, updatedAt: now };
|
|
187
|
+
const stateViolation = checkNodeState(candidate);
|
|
188
|
+
if (stateViolation)
|
|
189
|
+
return { ok: false, violations: [stateViolation] };
|
|
190
|
+
return { ok: true, node: candidate };
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Append one evidence entry and optionally move the node's state.
|
|
194
|
+
*
|
|
195
|
+
* A state change that is not permitted by the resulting evidence is refused
|
|
196
|
+
* outright, so the stored record can never be inconsistent.
|
|
197
|
+
*
|
|
198
|
+
* @param node - the node being updated.
|
|
199
|
+
* @param evidence - the observation to record.
|
|
200
|
+
* @param options.now - ISO timestamp for `updatedAt`.
|
|
201
|
+
* @param options.state - optional new state.
|
|
202
|
+
* @returns either the updated record or the violation that rejected it.
|
|
203
|
+
*/
|
|
204
|
+
export function recordEvidence(node, evidence, options) {
|
|
205
|
+
const next = {
|
|
206
|
+
...node,
|
|
207
|
+
evidence: [...node.evidence, evidence],
|
|
208
|
+
state: options.state ?? node.state,
|
|
209
|
+
updatedAt: options.now,
|
|
210
|
+
};
|
|
211
|
+
const stateViolation = checkNodeState(next);
|
|
212
|
+
if (stateViolation)
|
|
213
|
+
return { ok: false, violations: [stateViolation] };
|
|
214
|
+
return { ok: true, node: next };
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* The state a node is allowed to be created in when the caller supplies none.
|
|
218
|
+
*
|
|
219
|
+
* @returns the born state.
|
|
220
|
+
*/
|
|
221
|
+
export function bornState() {
|
|
222
|
+
return INITIAL_NODE_STATE;
|
|
223
|
+
}
|
|
224
|
+
//# sourceMappingURL=diagnosis.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diagnosis.js","sourceRoot":"","sources":["../src/diagnosis.ts"],"names":[],"mappings":"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"}
|