dsh-diagnostic-tutor 0.1.1 → 0.2.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.
Files changed (115) hide show
  1. package/README.md +224 -59
  2. package/lib/api.js +178 -0
  3. package/lib/api.js.map +1 -1
  4. package/lib/block-types.js +25 -0
  5. package/lib/block-types.js.map +1 -0
  6. package/lib/client.js +9448 -1654
  7. package/lib/client.js.map +1 -1
  8. package/lib/diagnostics.js +170 -0
  9. package/lib/diagnostics.js.map +1 -0
  10. package/lib/index.js +75 -13
  11. package/lib/index.js.map +1 -1
  12. package/lib/insights.js +82 -0
  13. package/lib/insights.js.map +1 -0
  14. package/lib/interactions.js +70 -0
  15. package/lib/interactions.js.map +1 -0
  16. package/lib/journal.js +122 -0
  17. package/lib/journal.js.map +1 -0
  18. package/lib/lesson-markdown.js +72 -0
  19. package/lib/lesson-markdown.js.map +1 -0
  20. package/lib/lesson.js +152 -3
  21. package/lib/lesson.js.map +1 -1
  22. package/lib/math.js +126 -0
  23. package/lib/math.js.map +1 -0
  24. package/lib/path-api.js +140 -0
  25. package/lib/path-api.js.map +1 -0
  26. package/lib/path-runtime.js +361 -0
  27. package/lib/path-runtime.js.map +1 -0
  28. package/lib/path-tools.js +107 -0
  29. package/lib/path-tools.js.map +1 -0
  30. package/lib/path.js +179 -0
  31. package/lib/path.js.map +1 -0
  32. package/lib/plan-tools.js +89 -0
  33. package/lib/plan-tools.js.map +1 -0
  34. package/lib/plan.js +82 -0
  35. package/lib/plan.js.map +1 -0
  36. package/lib/project-session.js +48 -0
  37. package/lib/project-session.js.map +1 -0
  38. package/lib/prompt.js +4 -19
  39. package/lib/prompt.js.map +1 -1
  40. package/lib/review-sync.js +51 -0
  41. package/lib/review-sync.js.map +1 -0
  42. package/lib/review-tools.js +115 -0
  43. package/lib/review-tools.js.map +1 -0
  44. package/lib/review.js +103 -0
  45. package/lib/review.js.map +1 -0
  46. package/lib/source-schema.js +24 -0
  47. package/lib/source-schema.js.map +1 -0
  48. package/lib/source-tools.js +61 -0
  49. package/lib/source-tools.js.map +1 -0
  50. package/lib/sources.js +78 -0
  51. package/lib/sources.js.map +1 -0
  52. package/lib/state.js +148 -7
  53. package/lib/state.js.map +1 -1
  54. package/lib/tools.js +456 -40
  55. package/lib/tools.js.map +1 -1
  56. package/lib/types/api.d.ts +7 -0
  57. package/lib/types/api.d.ts.map +1 -1
  58. package/lib/types/block-types.d.ts +12 -0
  59. package/lib/types/block-types.d.ts.map +1 -0
  60. package/lib/types/contract.d.ts +33 -1
  61. package/lib/types/contract.d.ts.map +1 -1
  62. package/lib/types/diagnostics.d.ts +95 -0
  63. package/lib/types/diagnostics.d.ts.map +1 -0
  64. package/lib/types/handoff.d.ts +2 -2
  65. package/lib/types/index.d.ts +15 -9
  66. package/lib/types/index.d.ts.map +1 -1
  67. package/lib/types/insights.d.ts +57 -0
  68. package/lib/types/insights.d.ts.map +1 -0
  69. package/lib/types/interactions.d.ts +23 -0
  70. package/lib/types/interactions.d.ts.map +1 -0
  71. package/lib/types/journal.d.ts +57 -0
  72. package/lib/types/journal.d.ts.map +1 -0
  73. package/lib/types/lesson-markdown.d.ts +11 -0
  74. package/lib/types/lesson-markdown.d.ts.map +1 -0
  75. package/lib/types/lesson.d.ts +697 -5
  76. package/lib/types/lesson.d.ts.map +1 -1
  77. package/lib/types/math.d.ts +50 -0
  78. package/lib/types/math.d.ts.map +1 -0
  79. package/lib/types/path-api.d.ts +5 -0
  80. package/lib/types/path-api.d.ts.map +1 -0
  81. package/lib/types/path-runtime.d.ts +1558 -0
  82. package/lib/types/path-runtime.d.ts.map +1 -0
  83. package/lib/types/path-tools.d.ts +4 -0
  84. package/lib/types/path-tools.d.ts.map +1 -0
  85. package/lib/types/path.d.ts +744 -0
  86. package/lib/types/path.d.ts.map +1 -0
  87. package/lib/types/plan-tools.d.ts +4 -0
  88. package/lib/types/plan-tools.d.ts.map +1 -0
  89. package/lib/types/plan.d.ts +53 -0
  90. package/lib/types/plan.d.ts.map +1 -0
  91. package/lib/types/project-session.d.ts +33 -0
  92. package/lib/types/project-session.d.ts.map +1 -0
  93. package/lib/types/prompt.d.ts.map +1 -1
  94. package/lib/types/review-sync.d.ts +17 -0
  95. package/lib/types/review-sync.d.ts.map +1 -0
  96. package/lib/types/review-tools.d.ts +4 -0
  97. package/lib/types/review-tools.d.ts.map +1 -0
  98. package/lib/types/review.d.ts +65 -0
  99. package/lib/types/review.d.ts.map +1 -0
  100. package/lib/types/source-schema.d.ts +31 -0
  101. package/lib/types/source-schema.d.ts.map +1 -0
  102. package/lib/types/source-tools.d.ts +4 -0
  103. package/lib/types/source-tools.d.ts.map +1 -0
  104. package/lib/types/sources.d.ts +33 -0
  105. package/lib/types/sources.d.ts.map +1 -0
  106. package/lib/types/state.d.ts +516 -9
  107. package/lib/types/state.d.ts.map +1 -1
  108. package/lib/types/tools.d.ts +3 -1
  109. package/lib/types/tools.d.ts.map +1 -1
  110. package/lib/types/version.d.ts +11 -0
  111. package/lib/types/version.d.ts.map +1 -0
  112. package/lib/types/vocabulary.d.ts +2 -2
  113. package/lib/version.js +26 -0
  114. package/lib/version.js.map +1 -0
  115. package/package.json +20 -18
package/README.md CHANGED
@@ -11,24 +11,35 @@ learn interactively, and move to the next best step.
11
11
 
12
12
  ## Status
13
13
 
14
- `v0.1.1` — the current stable release. Conversation-scoped projects, focus,
15
- next steps and handoffs keep each learning session separate, while maps and
16
- lessons remain shared. The Learning UI now offers one next action and reports
17
- its actual state, with working scroll containers in docked and expanded views.
18
-
19
- Install the prebuilt package with
20
- `dsh plugin --profile web add dsh-diagnostic-tutor@0.1.1` — no clone, no
21
- `pnpm install`, no build step.
14
+ `v0.2.0` is the published release. It includes the learning-path flow — saved
15
+ goal and time budget, evidence-referenced initial assessment, versioned course
16
+ routes with per-lesson Overviews, session-scoped attempts, and the
17
+ English/Simplified-Chinese interface — plus the content/input/review loop:
18
+
19
+ - typeset math, formula/code/quiz/flashcard/resource/comparison blocks;
20
+ - learner sources with `sourceRefs` citations;
21
+ - Today plan with ICS export and learner-controlled completion;
22
+ - spaced review with automatic flashcard cards, four ratings and undo;
23
+ - read-only Insights.
24
+
25
+ Install it with
26
+ `dsh plugin --profile web add dsh-diagnostic-tutor@0.2.0` — no clone, no
27
+ `pnpm install`, no build step. A checkout can be installed instead (see
28
+ [Installing a checkout](#installing-a-checkout-instead-of-the-package)).
22
29
 
23
30
  The learning loop works end to end: a real goal in your own words, a diagnosis
24
31
  map grown one node at a time, a tutor-written lesson, a check answered in the
25
32
  chat, recorded evidence, and a next step the tutor chose — which the learner
26
- presses Continue to take.
33
+ presses Continue to take. The route flow adds a full plan the learner reviews
34
+ and accepts, lesson by lesson; the content loop adds math, sources, Today and
35
+ review on top of it.
27
36
 
28
37
  It is also early. The [limitations](#known-limitations) are listed rather than
29
38
  left to be discovered, and nothing below claims a capability that is not there:
30
- no PDF or document ingestion, no RAG, no flashcards, no resource libraries, no
31
- analytics, no course generation.
39
+ no PDF extraction, no RAG, no automatic external resource fetching, no analytics.
40
+ Review scheduling is a local interval heuristic, not FSRS. Course routes are
41
+ proposed by the teaching model, never generated by the runtime, and a route never
42
+ marks knowledge as mastered.
32
43
 
33
44
  ## Two halves, one system
34
45
 
@@ -124,12 +135,13 @@ see is a *diagnosis map*, not a syllabus.
124
135
 
125
136
  Stated plainly, because each one is a real edge a user can reach.
126
137
 
127
- **Math is styled, not typeset.** The tutor writes LaTeX by convention — `\(...\)`
128
- inline, `\[...\]` display — and the runtime sets it apart in a monospace face
129
- with its own background. It does **not** render it. Real typesetting needs a
130
- library plus fonts and CSS, and the client bundle is a single JavaScript file
131
- with nowhere to serve those from. This is the most visible rough edge in any
132
- STEM lesson.
138
+ **Math is rendered as MathML on the host.** The tutor writes LaTeX by convention
139
+ — `\(...\)` inline, `\[...\]` display — and the host pre-renders it with
140
+ KaTeX's MathML output before storing the block. The browser renders native
141
+ MathML through a tag/attribute whitelist; no font or CSS is fetched. Invalid
142
+ TeX inside a text block falls back to the old styled source; an invalid
143
+ `formula` block is refused at write time. Browser MathML quality varies, and
144
+ this build ships no KaTeX CSS/fonts.
133
145
 
134
146
  **Skill presence requires a live Agent.** The runtime checks the open
135
147
  conversation's Agent scope on each overview, including its workspace. Before an
@@ -157,9 +169,12 @@ matrix as load-bearing, not decoration.
157
169
 
158
170
  | This plugin | Verified against DSH | Node |
159
171
  | --- | --- | --- |
160
- | `0.1.1` | `0.2.1-rc.2`, `0.2.0-rc.2` (test suite also on `0.1.7-alpha.2`) | `^22.19.0 \|\| >=24.0.0` |
172
+ | `0.2.0` | `0.2.1-rc.3`, `0.2.1-rc.2`, `0.2.0-rc.2` (test suite also on `0.1.7-alpha.2`) | `^22.19.0 \|\| >=24.0.0` |
173
+ | `0.1.1` | `0.2.1-rc.3`, `0.2.1-rc.2`, `0.2.0-rc.2` (test suite also on `0.1.7-alpha.2`) | `^22.19.0 \|\| >=24.0.0` |
161
174
  | `0.1.0` | `0.1.7-alpha.2` (also composed under `0.1.5-rc.1`) | `^22.19.0 \|\| >=24.0.0` |
162
175
 
176
+ Phase 1–6 的本机 boot 矩阵(2026-10-29)覆盖 `0.2.1-rc.3`、`0.2.1-rc.2`、`0.2.0-rc.2`、`0.1.7-alpha.2` 的插件加载、本地路由、domain v1 与客户端启动;其中 rc3/alpha.2 还通过了 tarball 安装,并用真实模型完成 lesson → Review 评分/撤销 → Insights;rc2/rc20 的完整真实会话仍是补充项。详见 [`docs/planning/COMPATIBILITY.md`](docs/planning/COMPATIBILITY.md)。
177
+
163
178
  Rules this repository enforces mechanically:
164
179
 
165
180
  - **Named exports only.** A `export default` plugin makes Cordis' loader prefer
@@ -174,6 +189,38 @@ Rules this repository enforces mechanically:
174
189
  in the range — so the compatibility matrix above, not the range floor, is the
175
190
  contract. Dev dependencies pin one exact cohort for the type baseline.
176
191
 
192
+ ## UI baseline for redesign
193
+
194
+ The current surfaces, state model and screenshots (generated from fixtures) live in
195
+ [`docs/design/ui-baseline/index.html`](docs/design/ui-baseline/index.html) with a
196
+ detailed handoff in
197
+ [`docs/design/ui-baseline/README.md`](docs/design/ui-baseline/README.md). It covers
198
+ the system structure, Learning map / Current lesson surfaces, every route-page state
199
+ and the design constraints a new UI must keep.
200
+
201
+ ## Diagnostics
202
+
203
+ Monitoring is local and content-free: no chat, goal, lesson or evidence text is
204
+ recorded, and nothing is sent anywhere.
205
+
206
+ ```sh
207
+ pnpm doctor # human-readable health report
208
+ node scripts/doctor.mjs --json # machine-readable
209
+ node scripts/doctor.mjs --strict # exit 1 if an error-severity issue exists
210
+ ```
211
+
212
+ It reads `<dsh-home>/storages/udt.json` plus the event journal and reports each
213
+ project's stage, pending/failed tutor requests (with age), stalled attempts and
214
+ route-length conflicts. The same report is available at
215
+ `GET /diagnostic-tutor/api/diagnostics` (loopback-only, like every plugin
216
+ route).
217
+
218
+ The host journal lives at `<dsh-home>/logs/diagnostic-tutor-events.jsonl`
219
+ (`DSH_DIAGNOSTIC_TUTOR_EVENTS` overrides the path, `off` disables it). It logs
220
+ lifecycle events only: tool names and operations, durations, request outcomes,
221
+ provider error messages (truncated). A single file is capped at 1 MB and
222
+ rotated once.
223
+
177
224
  ---
178
225
 
179
226
  ## Install
@@ -182,7 +229,7 @@ Rules this repository enforces mechanically:
182
229
 
183
230
  | | |
184
231
  | --- | --- |
185
- | [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) | verified against `0.2.1-rc.2` |
232
+ | [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) | verified against `0.2.1-rc.3` |
186
233
  | the [Universal Diagnostic Tutor](https://github.com/SenmuuuuW/universal-diagnostic-tutor-skill) skill | the teaching brain. Without it the runtime loads, records and displays state, but **no lesson is ever written** |
187
234
  | Node | `^22.19.0 \|\| >=24.0.0` |
188
235
 
@@ -234,7 +281,7 @@ learn?*.
234
281
 
235
282
  ```sh
236
283
  npm pack
237
- dsh plugin --profile web add "$PWD/dsh-diagnostic-tutor-0.1.1.tgz"
284
+ dsh plugin --profile web add "$PWD/dsh-diagnostic-tutor-0.2.0.tgz"
238
285
  ```
239
286
 
240
287
  **What gets installed is prebuilt.** The package ships `lib/` — the host half
@@ -321,7 +368,7 @@ route around them:
321
368
 
322
369
  ### Tools
323
370
 
324
- Six, and none of them decides anything about teaching.
371
+ Eighteen. None of them decides anything about teaching; they record or report.
325
372
 
326
373
  | Tool | Does |
327
374
  | --- | --- |
@@ -329,8 +376,20 @@ Six, and none of them decides anything about teaching.
329
376
  | `udt_goal_create` | records a goal and plants only the map root; may also select it for this conversation |
330
377
  | `udt_map_get` | reads the selected project's map, or an explicitly named shared course, with each node's relation, state and evidence |
331
378
  | `udt_map_update` | `add-nodes` · `set-state` · `add-evidence` |
332
- | `udt_lesson_update` | writes teaching into the learning surface as blocks |
379
+ | `udt_lesson_update` | writes teaching into the learning surface as blocks; pre-renders math; attaches source citations |
380
+ | `udt_quiz_update` | records a quiz/flashcard submission; never grades and never changes node state |
333
381
  | `udt_decide_next` | records where the learner should go next, and why |
382
+ | `udt_path_get` | reads the goal brief, assessment, routes and this conversation's active attempt |
383
+ | `udt_path_update` | saves a brief, assessment or route proposal |
384
+ | `udt_course_run_update` | binds a diagnosed node to an attempt, or completes with evidence |
385
+ | `udt_source_add` | stores pasted text or one explicitly named local `.md`/`.txt` file; deduplicates by SHA-256 |
386
+ | `udt_source_list` | lists source metadata without content |
387
+ | `udt_source_get` | returns one stored source for citation |
388
+ | `udt_plan_get` | reads the learner-owned Today plan |
389
+ | `udt_plan_update` | adds, completes or removes plan items; planning is policy, not teaching |
390
+ | `udt_review_add` | queues review cards; scheduling is policy, not mastery |
391
+ | `udt_review_next` | reads cards due now |
392
+ | `udt_review_grade` | records a review rating and reschedules; never confirms a node |
334
393
 
335
394
  The division is the architecture: the tutor decides **what** to teach, when to
336
395
  check, and what an answer showed; the runtime decides **what may be stored** and
@@ -404,20 +463,19 @@ the button means.
404
463
 
405
464
  | Surface | Where | For |
406
465
  | --- | --- | --- |
407
- | **Learning tab** | right sidebar, beside the chat | everyday work — map, node and lesson while you talk |
408
- | **Learning panel** | the main column (`main`) | focus mode — the whole runtime at once |
409
-
410
- The tab is the reason the loop is usable: the full panel fills the main column,
411
- which is also where the conversation lives, so with only that panel answering a
412
- check meant leaving the lesson. The right sidebar is a separate column.
466
+ | **Learning map** | the main column (`main`, key `diagnostic-tutor-map`) | browsing — course route, every lesson Overview, knowledge & evidence |
467
+ | **Current lesson** | the right sidebar tab, beside the chat | the lesson itself — teaching units, and checks answered in the chat |
413
468
 
414
- Both run on one shared `useLearning` state, so they cannot disagree about what
415
- is focused or what the tutor wrote. The panel's **Answer in the chat** button
416
- returns to the conversation and docks the tab in the same step.
469
+ One page for browsing, one surface for learning. The page never starts a lesson;
470
+ the tab never decides what to teach. Both read the same `useLearning` state and
471
+ the same path client, so they cannot disagree about what is focused, accepted or
472
+ written. **Open current lesson** on the map returns to the conversation with the
473
+ lesson tab open in the same step.
417
474
 
418
- The right sidebar hosts session-scoped tabs, so it can only accept one while a
419
- session surface is mounted — which is why docking happens on the way back to the
420
- conversation rather than at load.
475
+ The container for the main column is the map page. A full-page lesson panel used
476
+ to exist as a second main destination; it was removed so the same lesson is not
477
+ offered in two places. The standalone full-page component is still used by the
478
+ preview and the tests, but the plugin does not register it.
421
479
 
422
480
  ### Teaching-brain detection
423
481
 
@@ -450,20 +508,76 @@ which judged an answer is *not finished* until the next step is recorded — so
450
508
  the bridge was **deleted rather than kept as a second voice**. The plugin got
451
509
  better at it, which is the evidence it belonged upstream.
452
510
 
453
- ## The panel
454
-
455
- The browser half registers two things and nothing else: a sidebar icon
456
- (`sidebar.panellist`, a `list`) and the page it opens (`main`, a `keyed` slot).
457
- The sidebar `id` and the panel `key` come from one constant — a drift between
458
- them would leave the icon opening nothing.
459
-
460
- It reads as one sentence, left to right:
511
+ ## The page and the lesson surface
512
+
513
+ The browser half registers three things: the **Learning map** icon in the
514
+ sidebar (`sidebar.panellist`, a `list`), the page it opens (`main`, a `keyed`
515
+ slot, key `diagnostic-tutor-map`), and the **Current lesson** tab in the right
516
+ sidebar (a tab type plus its body and title). The map's sidebar `id` and its
517
+ `main` key come from one constant — a drift between them would leave the icon
518
+ opening nothing.
519
+
520
+ The page has two tabs: **Course route** (the versioned plan, route canvas,
521
+ ordered lesson list and every Overview) and **Knowledge & evidence** (the
522
+ diagnosis map and its recorded observations). Neither one teaches: they show
523
+ stored state.
524
+
525
+ The route tab shows one stage at a time and one next step. A progress bar names
526
+ the four stages — goal & time, initial diagnosis, route, learning — and a
527
+ **Next step** card always answers "what now?" with a single primary action
528
+ (set the goal, request or answer the diagnosis, request the route, accept the
529
+ version, start the first lesson, continue the current one) plus at most one
530
+ real alternative. Competing action rows live in the folded history, not beside
531
+ it. Before a goal is saved the wizard owns the page; while the tutor is asking
532
+ diagnostic questions the card points back to the conversation, shows how long it
533
+ has waited, says whether the tutor is still thinking, and on a failed turn
534
+ retries with the current model and opens the chat (the failure is recorded on
535
+ the path request, so it survives a restart; provider errors are translated into
536
+ billing / auth / rate-limit / network / context advice); route versions
537
+ appear only after the diagnosis is complete or skipped; once accepted, the page
538
+ leads with the route canvas. A proposed or accepted route shows its total study
539
+ time, the weeks it takes at the saved weekly budget, and any lessons longer than
540
+ the preferred session. Next step moves to the first unfinished lesson after a
541
+ completion, and says when the whole route is done. Completed diagnosis details
542
+ and route settings stay reachable in folded sections.
543
+
544
+ The plugin's own accent is **DeepSeek's brand blue** — `#4d6bfe` on the light
545
+ canvas, `#6a84ff` on the dark one — used sparingly for progress, the active tab,
546
+ the current/selected item, focus rings and action links. Primary buttons keep the
547
+ host's near-black/white fill: the blue marks, it does not act. All other colours
548
+ continue to come from the host theme.
549
+
550
+ The initial diagnosis is asked through DSH's **interactive question card**
551
+ (`ask_user_question`) when the profile provides it: options are tapped rather
552
+ than typed, multi-select works, and a free-text question covers explanations.
553
+ The tutor still owns the questions, evidence and assessment; profiles without
554
+ that tool fall back to chat text.
555
+
556
+ The lesson lives in the docked tab, where the conversation is still on screen.
557
+ That is what makes the check loop work without leaving the lesson. The sidebar
558
+ action **Learn** opens that tab and never takes over the main column; the map's
559
+ **Open current lesson** button returns to the conversation and opens the same
560
+ tab.
461
561
 
462
562
  ```
463
- [ Course + Diagnosis Map ] → [ Selected node ] → [ Learning surface ]
563
+ sidebar Learn ─────────────▶ Current lesson (right sidebar)
564
+ sidebar Learning map ──────▶ Learning map (main column: route | knowledge)
565
+
566
+ Open current lesson ◀────── returns to chat + lesson tab
464
567
  ```
465
568
 
466
- There is no dashboard: three panes, and the map is the navigation.
569
+ Entering learning follows the conversation, not the window. A project is
570
+ shared, but its focus and planned runs belong to a conversation, so the project
571
+ library and **Open current lesson** ask the host which conversation was last
572
+ learning the project (`GET /project/session`, read-only) and switch there with
573
+ the shell's session navigation when it differs. The order is: newest active
574
+ focus, then newest course run with an attempt, then newest project selection;
575
+ only a project no conversation has ever touched falls back to the conversation
576
+ on screen.
577
+
578
+ A full-page lesson panel used to exist as a second main destination. It was
579
+ removed: one page for browsing, one surface for learning, and the conversation
580
+ keeps the main column.
467
581
 
468
582
  ### Learning Blocks
469
583
 
@@ -473,25 +587,42 @@ keyed by `type`:
473
587
 
474
588
  | Type | Content |
475
589
  | --- | --- |
476
- | `text` | `md` — markdown, with the skill's `\(...\)` math convention |
590
+ | `text` | `md` — markdown, plus host `math[]` fragments for typeset formulas |
477
591
  | `example` | `title`, `steps[]`, `takeaway?` |
478
592
  | `diagram` | `format` (`ascii` \| `mermaid`), `spec`, `caption?` |
479
593
  | `check` | `prompt`, `expect?`, `hint?` — the stop-and-wait surface |
480
-
481
- Adding Formula, Code, Comparison, Practice or Resource later means adding one
482
- registry entry, never rewriting the lesson renderer. An **unknown type renders a
483
- readable placeholder** rather than throwing, so a lesson authored by a newer
484
- host still renders here.
594
+ | `formula` | `tex`, `display?`, host-rendered `mathml`, `caption?` |
595
+ | `code` | `language?`, `source`, `caption?` — displayed, never executed |
596
+ | `quiz` | `title?`, `questions[]` (`mcq`/`short`); answers are recorded, never graded by the runtime |
597
+ | `flashcard` | `front`, `back`, `tags?`; self-report is recorded, not scheduled as mastery |
598
+ | `resource` | `title`, optional `sourceId`/`locator`/`url`/`note` |
599
+ | `comparison` | `columns[]`, `rows[][]`, `caption?` |
600
+
601
+ Blocks may also carry `sourceRefs: [{sourceId, locator}]`; every reference is
602
+ checked against the `sources` table at write time. The stored schema accepts an
603
+ **unknown type** from a newer host and the client renders a readable placeholder;
604
+ the current model tool still writes only known types.
605
+ The Learning map also shows a compact **Today** card when `udt_plan_update` has created open plan items, with a done action and an **Export calendar** action for dated items. DSH's own `schedule` is session-local, so this is an explicit calendar export rather than a push notification. Review cards and their local interval scheduler are stored but have no separate review surface yet.
485
606
 
486
607
  ### Browser API
487
608
 
488
- Three calls, at `/diagnostic-tutor/api`:
609
+ The browser's core calls, at `/diagnostic-tutor/api` (the full route list is in
610
+ [the architecture notes](docs/planning/CURRENT-ARCHITECTURE.md)):
489
611
 
490
612
  | Route | Returns |
491
613
  | --- | --- |
492
614
  | `GET /overview` | the current course and its whole map (`course: null` on a first run) |
493
615
  | `GET /node?id=` | one node with its evidence, parent and children |
616
+ | `GET /project/session?courseId=` | the conversation that should resume a project (read-only) |
494
617
  | `POST /lesson {nodeId}` | the prototype lesson, built once and reused after that |
618
+ | `GET /source?id=` | one stored source and its text, for a citation preview |
619
+ | `POST /path/answer` | record a quiz/flashcard submission on the active attempt |
620
+ | `GET /plan/ics` | download open, dated Today plan items as an ICS calendar |
621
+ | `GET /review` | due review cards (read-only) |
622
+ | `POST /review/grade` | record a review rating and reschedule |
623
+ | `POST /review/undo` | restore the previous schedule after the latest grade |
624
+ | `GET /insights?courseId=` | read-only course insight counts |
625
+ | `POST /plan/task` | mark a Today plan item done (learner action; plan only) |
495
626
 
496
627
  Every request passes a **trust fence**: a bare `ctx.webServer.register()` route
497
628
  inherits no authentication, so the route checks that the request arrived at a
@@ -499,6 +630,27 @@ loopback `Host`, from a loopback `Origin`, and is not marked cross-site. Anythin
499
630
  else gets `403` and no body. The browser receives **views only** — no storage
500
631
  path, no domain handle, no raw record.
501
632
 
633
+ ### Interface language
634
+
635
+ Every plugin surface ships copy in **English and Simplified Chinese**: the
636
+ Learning map page, the course route, the docked Current-lesson tab, the
637
+ standalone full-page panel used by the preview and tests, and the shared
638
+ next-step card, handoff line and welcome row. The sidebar Learn action and its
639
+ missing-conversation explanation switch too. A toggle in the map header (and in
640
+ the lesson surfaces) changes the copy in place; the language is saved on the
641
+ learner profile (`preferredLanguage`, via `GET /preferences` and
642
+ `POST /preferences/language`), so it survives a restart and applies to every
643
+ conversation. The choice applies immediately and is saved in the background: a
644
+ failed request keeps the local choice rather than flipping the UI back.
645
+
646
+ It is copy, not content: the tutor's teaching, the learner's goal text, lesson
647
+ overviews, node titles and recorded evidence are never machine-translated, and
648
+ the model is not instructed to change language by the toggle. Three things stay
649
+ outside the switch: the **Learning map** sidebar label (registered once when the
650
+ plugin loads, so it cannot follow an in-place change), server/model error text
651
+ and dynamic handoff details as generated, and the development preview's own
652
+ toolbar.
653
+
502
654
  ## Preview
503
655
 
504
656
  The panel takes its API as a prop, so the UI runs with no DSH and no agent:
@@ -522,8 +674,10 @@ Machine Learning [goal, unconfirmed]
522
674
 
523
675
  Two query parameters reach the states that are otherwise awkward to produce on
524
676
  demand — `?scenario=` (`first-use`, `map`, `handoff`, `next`, `stay`, `noskill`,
525
- `long`, `scroll`, `library`, `new`) and `?surface=tab` for the docked right-sidebar
526
- tab. The scenario switch is in the page's toolbar, and the theme toggle sets the same
677
+ `long`, `scroll`, `library`, `new`, `showcase`, `curriculum`, `path-draft`,
678
+ `path-setup`, `path-assessment`) and `?surface=tab` / `?surface=map` for the
679
+ docked right-sidebar tab and the standalone map page. The scenario switch is in
680
+ the page's toolbar, and the theme toggle sets the same
527
681
  `body[data-ds-dark-theme]` attribute DSH does. The page carries the real DSH
528
682
  design tokens for the same reason: with hardcoded fallbacks in their place, a
529
683
  token that resolves to near-white in dark mode looks perfectly fine locally.
@@ -566,15 +720,19 @@ What the surfaces look like now, captured from a live profile:
566
720
 
567
721
  | | light | dark |
568
722
  | --- | --- | --- |
569
- | full panel | ![panel, light](preview/ui-polish/panel-light-1440.png) | ![panel, dark](preview/ui-polish/panel-dark-1440.png) |
723
+ | standalone panel (preview/tests only) | ![panel, light](preview/ui-polish/panel-light-1440.png) | ![panel, dark](preview/ui-polish/panel-dark-1440.png) |
570
724
  | docked tab | ![tab, light](preview/ui-polish/tab-light-400.png) | ![tab, dark](preview/ui-polish/tab-dark-400.png) |
571
725
 
726
+ These captures predate the current layout: the standalone full-page panel is no
727
+ longer a product surface. Today the Learning map page owns the main column and
728
+ the Current lesson tab holds the teaching.
729
+
572
730
  ## Development
573
731
 
574
732
  ```sh
575
733
  pnpm install
576
734
  pnpm typecheck # tsc --noEmit (host and client)
577
- pnpm test # 455 tests: unit, guard, DOM, render, real composition
735
+ pnpm test # unit, guard, DOM, render, real composition
578
736
  pnpm build # tsc -> lib/ (host) + tsdown -> lib/client.js
579
737
  ```
580
738
 
@@ -617,8 +775,10 @@ shows a loader row exists.
617
775
  | `v0.0.10` | export and delete your data |
618
776
  | `v0.0.11` | storage layout tested; `single` kept, with the reason |
619
777
  | `v0.1.0-rc.1` | packaged install, no clone or build required |
620
- | `v0.1.0` | **current** — first stable release, installable from npm |
621
- | `v0.2.0` | settings, i18n, math typesetting |
778
+ | `v0.1.0` | first stable release, installable from npm |
779
+ | `v0.1.1` | packaged release with the docked learning surface |
780
+ | `v0.2.0` | **published** — Phase 1–6 content/input/review loop, learning-path flow, Today/Review/Insights |
781
+ | next | settings, automatic replan suggestions (the tutor already proposes revisions on request; no runtime auto-replan) |
622
782
 
623
783
  ## Trust
624
784
 
@@ -626,7 +786,10 @@ DSH does not sandbox plugin code: an installed plugin runs in-process with your
626
786
  privileges, and a bare `ctx.webServer.register()` route inherits no
627
787
  authentication. This plugin's commitments:
628
788
 
629
- - reads and writes only its own storage domain;
789
+ - reads and writes only its own storage domain, with one explicit exception:
790
+ `udt_source_add(path)` reads exactly one regular `.md`/`.markdown`/`.txt` file
791
+ the caller names, once, then stores its text in that domain. It never scans a
792
+ directory, follows a symlink, accepts a URL, or fetches anything;
630
793
  - serves its browser half only from loopback-fenced routes it checks itself;
631
794
  - makes no outbound network requests;
632
795
  - writes no files outside the harness's own storage;
@@ -727,4 +890,6 @@ MIT. The Universal Diagnostic Tutor skill is a separate MIT project by the same
727
890
  author and is not vendored here.
728
891
 
729
892
  Planning and architecture research for this project live in
730
- [`docs/planning/`](docs/planning/).
893
+ [`docs/planning/`](docs/planning/). Phase 1–6 merge order, acceptance checks and the DSH compatibility checklist are in
894
+ [`docs/planning/PHASE-1-6-MERGE.md`](docs/planning/PHASE-1-6-MERGE.md). 0.2.0 发布清单与验证记录见
895
+ [`docs/planning/RELEASE-0.2.0.md`](docs/planning/RELEASE-0.2.0.md).