@intellectif/lk-react 21.0.0 → 23.0.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 (166) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/README.md +4 -4
  3. package/dist/{WrittenResponse-BVPmrmQL.d.cts → WrittenResponse-CgRrjM4S.d.ts} +14 -4
  4. package/dist/{WrittenResponse-IAJvvL-g.d.ts → WrittenResponse-D2K1VW_V.d.cts} +14 -4
  5. package/dist/ai/LkAiProvider.cjs +3 -3
  6. package/dist/ai/LkAiProvider.d.cts +12 -1
  7. package/dist/ai/LkAiProvider.d.ts +12 -1
  8. package/dist/ai/LkAiProvider.js +1 -1
  9. package/dist/ai/useAiHelp.cjs +9 -5
  10. package/dist/ai/useAiHelp.d.cts +58 -3
  11. package/dist/ai/useAiHelp.d.ts +58 -3
  12. package/dist/ai/useAiHelp.js +3 -3
  13. package/dist/{chunk-M3K6BF5M.js → chunk-2OP5YHRR.js} +13 -8
  14. package/dist/chunk-2OP5YHRR.js.map +1 -0
  15. package/dist/{chunk-AY3WN4FB.js → chunk-4MXECBZT.js} +9 -9
  16. package/dist/chunk-4MXECBZT.js.map +1 -0
  17. package/dist/{chunk-EANGKAKG.cjs → chunk-54DX6SLS.cjs} +12 -2
  18. package/dist/chunk-54DX6SLS.cjs.map +1 -0
  19. package/dist/{chunk-WNDNQ6NR.js → chunk-6GQLCCGN.js} +11 -13
  20. package/dist/chunk-6GQLCCGN.js.map +1 -0
  21. package/dist/{chunk-Y4IDF5UO.cjs → chunk-6RIM5EQP.cjs} +140 -9
  22. package/dist/chunk-6RIM5EQP.cjs.map +1 -0
  23. package/dist/{chunk-NILHU555.cjs → chunk-7F2YQWMW.cjs} +17 -12
  24. package/dist/chunk-7F2YQWMW.cjs.map +1 -0
  25. package/dist/{chunk-YWJ5XJDU.cjs → chunk-B4JRBV56.cjs} +6 -6
  26. package/dist/{chunk-YWJ5XJDU.cjs.map → chunk-B4JRBV56.cjs.map} +1 -1
  27. package/dist/{chunk-CB2A56Q7.cjs → chunk-B7JU52YQ.cjs} +32 -35
  28. package/dist/chunk-B7JU52YQ.cjs.map +1 -0
  29. package/dist/{chunk-EYLBSS4Y.js → chunk-C4PSJ3ZC.js} +10 -2
  30. package/dist/chunk-C4PSJ3ZC.js.map +1 -0
  31. package/dist/{chunk-NDIKHMLJ.js → chunk-CXIP7ERK.js} +138 -8
  32. package/dist/chunk-CXIP7ERK.js.map +1 -0
  33. package/dist/{chunk-ZDDBWUUA.js → chunk-DPNCCWOV.js} +120 -27
  34. package/dist/chunk-DPNCCWOV.js.map +1 -0
  35. package/dist/{chunk-PPFVTXGJ.js → chunk-DXFCYNST.js} +11 -3
  36. package/dist/chunk-DXFCYNST.js.map +1 -0
  37. package/dist/{chunk-WXESKL75.cjs → chunk-DZ7VLLDQ.cjs} +30 -30
  38. package/dist/chunk-DZ7VLLDQ.cjs.map +1 -0
  39. package/dist/{chunk-U6SEII5V.js → chunk-E4UF4XZM.js} +4 -4
  40. package/dist/{chunk-U6SEII5V.js.map → chunk-E4UF4XZM.js.map} +1 -1
  41. package/dist/{chunk-XM5Q2ZCH.js → chunk-ERV666AR.js} +10 -13
  42. package/dist/chunk-ERV666AR.js.map +1 -0
  43. package/dist/{chunk-6SNNMVBV.cjs → chunk-FISEZBLF.cjs} +7 -7
  44. package/dist/{chunk-6SNNMVBV.cjs.map → chunk-FISEZBLF.cjs.map} +1 -1
  45. package/dist/{chunk-X4YWXZ4J.js → chunk-HYOUMTPW.js} +3 -3
  46. package/dist/{chunk-X4YWXZ4J.js.map → chunk-HYOUMTPW.js.map} +1 -1
  47. package/dist/{chunk-5Q2KFIZQ.js → chunk-HZIT24BK.js} +4 -4
  48. package/dist/{chunk-5Q2KFIZQ.js.map → chunk-HZIT24BK.js.map} +1 -1
  49. package/dist/{chunk-GPGCBDBN.cjs → chunk-IYGF7PZY.cjs} +24 -24
  50. package/dist/{chunk-GPGCBDBN.cjs.map → chunk-IYGF7PZY.cjs.map} +1 -1
  51. package/dist/{chunk-XB2AHEU5.js → chunk-JMEZYYCE.js} +11 -11
  52. package/dist/{chunk-XB2AHEU5.js.map → chunk-JMEZYYCE.js.map} +1 -1
  53. package/dist/{chunk-KRM6DX47.js → chunk-KI6DJ5ZF.js} +10 -13
  54. package/dist/chunk-KI6DJ5ZF.js.map +1 -0
  55. package/dist/{chunk-WGHMWBEJ.cjs → chunk-MXZHL4U5.cjs} +29 -31
  56. package/dist/chunk-MXZHL4U5.cjs.map +1 -0
  57. package/dist/{chunk-GHKMX5UT.cjs → chunk-NFGUJ52G.cjs} +10 -2
  58. package/dist/chunk-NFGUJ52G.cjs.map +1 -0
  59. package/dist/{chunk-W6TWQY6S.js → chunk-O7MINPKZ.js} +10 -10
  60. package/dist/chunk-O7MINPKZ.js.map +1 -0
  61. package/dist/{chunk-MPHX3Y42.cjs → chunk-OJGTJOWI.cjs} +2 -2
  62. package/dist/chunk-OJGTJOWI.cjs.map +1 -0
  63. package/dist/{chunk-W2P5RILE.cjs → chunk-PGAPZ6UE.cjs} +32 -35
  64. package/dist/chunk-PGAPZ6UE.cjs.map +1 -0
  65. package/dist/{chunk-OKEN6NJL.cjs → chunk-QAZO3ANW.cjs} +32 -32
  66. package/dist/chunk-QAZO3ANW.cjs.map +1 -0
  67. package/dist/{chunk-MCTNLUKO.js → chunk-RX3A6KIY.js} +2 -2
  68. package/dist/chunk-RX3A6KIY.js.map +1 -0
  69. package/dist/{chunk-LHFC2BCM.cjs → chunk-SHDSOOBF.cjs} +21 -21
  70. package/dist/{chunk-LHFC2BCM.cjs.map → chunk-SHDSOOBF.cjs.map} +1 -1
  71. package/dist/{chunk-U2BX2RWY.js → chunk-SPSKDNEE.js} +11 -11
  72. package/dist/{chunk-U2BX2RWY.js.map → chunk-SPSKDNEE.js.map} +1 -1
  73. package/dist/{chunk-5MO7ACSB.js → chunk-TGTP5DKF.js} +10 -10
  74. package/dist/{chunk-5MO7ACSB.js.map → chunk-TGTP5DKF.js.map} +1 -1
  75. package/dist/{chunk-GND3OBVE.js → chunk-U5VFJVQ2.js} +3 -3
  76. package/dist/{chunk-GND3OBVE.js.map → chunk-U5VFJVQ2.js.map} +1 -1
  77. package/dist/{chunk-7JBXDQTD.cjs → chunk-UKUHVCZX.cjs} +5 -5
  78. package/dist/{chunk-7JBXDQTD.cjs.map → chunk-UKUHVCZX.cjs.map} +1 -1
  79. package/dist/{chunk-M4UJBLVR.cjs → chunk-VPU6UY4O.cjs} +4 -4
  80. package/dist/{chunk-M4UJBLVR.cjs.map → chunk-VPU6UY4O.cjs.map} +1 -1
  81. package/dist/{chunk-CQ37UD4T.cjs → chunk-W6UMY7PF.cjs} +5 -5
  82. package/dist/{chunk-CQ37UD4T.cjs.map → chunk-W6UMY7PF.cjs.map} +1 -1
  83. package/dist/{chunk-GW3YIPDE.cjs → chunk-WZ2TBXVB.cjs} +18 -18
  84. package/dist/{chunk-GW3YIPDE.cjs.map → chunk-WZ2TBXVB.cjs.map} +1 -1
  85. package/dist/{chunk-XZCMUDFP.cjs → chunk-XS6OYYVR.cjs} +129 -36
  86. package/dist/chunk-XS6OYYVR.cjs.map +1 -0
  87. package/dist/{chunk-PJMXHDY5.js → chunk-XYJBRKBZ.js} +3 -3
  88. package/dist/{chunk-PJMXHDY5.js.map → chunk-XYJBRKBZ.js.map} +1 -1
  89. package/dist/components/ActivityPreview.cjs +17 -17
  90. package/dist/components/ActivityPreview.d.cts +2 -2
  91. package/dist/components/ActivityPreview.d.ts +2 -2
  92. package/dist/components/ActivityPreview.js +16 -16
  93. package/dist/components/ActivitySequence.cjs +18 -18
  94. package/dist/components/ActivitySequence.d.cts +2 -2
  95. package/dist/components/ActivitySequence.d.ts +2 -2
  96. package/dist/components/ActivitySequence.js +17 -17
  97. package/dist/components/Dictation.cjs +10 -10
  98. package/dist/components/Dictation.d.cts +1 -1
  99. package/dist/components/Dictation.d.ts +1 -1
  100. package/dist/components/Dictation.js +9 -9
  101. package/dist/components/FillInTheBlanks.cjs +10 -10
  102. package/dist/components/FillInTheBlanks.d.cts +1 -1
  103. package/dist/components/FillInTheBlanks.d.ts +1 -1
  104. package/dist/components/FillInTheBlanks.js +9 -9
  105. package/dist/components/GapSelect.cjs +10 -10
  106. package/dist/components/GapSelect.d.cts +1 -1
  107. package/dist/components/GapSelect.d.ts +1 -1
  108. package/dist/components/GapSelect.js +9 -9
  109. package/dist/components/InteractiveVideo.cjs +17 -17
  110. package/dist/components/InteractiveVideo.d.cts +2 -2
  111. package/dist/components/InteractiveVideo.d.ts +2 -2
  112. package/dist/components/InteractiveVideo.js +15 -15
  113. package/dist/components/MultipleChoice.cjs +10 -10
  114. package/dist/components/MultipleChoice.d.cts +1 -1
  115. package/dist/components/MultipleChoice.d.ts +1 -1
  116. package/dist/components/MultipleChoice.js +9 -9
  117. package/dist/components/PronunciationFeedback.cjs +5 -5
  118. package/dist/components/PronunciationFeedback.d.cts +1 -1
  119. package/dist/components/PronunciationFeedback.d.ts +1 -1
  120. package/dist/components/PronunciationFeedback.js +4 -4
  121. package/dist/components/ReadAloud.cjs +8 -8
  122. package/dist/components/ReadAloud.d.cts +2 -2
  123. package/dist/components/ReadAloud.d.ts +2 -2
  124. package/dist/components/ReadAloud.js +7 -7
  125. package/dist/components/StimulusPanel.cjs +4 -4
  126. package/dist/components/StimulusPanel.d.cts +1 -1
  127. package/dist/components/StimulusPanel.d.ts +1 -1
  128. package/dist/components/StimulusPanel.js +3 -3
  129. package/dist/components/WrittenResponse.cjs +9 -7
  130. package/dist/components/WrittenResponse.d.cts +3 -3
  131. package/dist/components/WrittenResponse.d.ts +3 -3
  132. package/dist/components/WrittenResponse.js +8 -6
  133. package/dist/i18n/LkIntlProvider.cjs +7 -7
  134. package/dist/i18n/LkIntlProvider.d.cts +2 -2
  135. package/dist/i18n/LkIntlProvider.d.ts +2 -2
  136. package/dist/i18n/LkIntlProvider.js +1 -1
  137. package/dist/index.cjs +45 -41
  138. package/dist/index.d.cts +3 -3
  139. package/dist/index.d.ts +3 -3
  140. package/dist/index.js +19 -19
  141. package/dist/theme/skin.css +23 -0
  142. package/dist/{types-Botf4Wqj.d.cts → types-CO0XLEN6.d.cts} +19 -0
  143. package/dist/{types-7lGlo0zE.d.ts → types-CrtIcRcK.d.ts} +19 -0
  144. package/package.json +3 -3
  145. package/dist/chunk-AY3WN4FB.js.map +0 -1
  146. package/dist/chunk-CB2A56Q7.cjs.map +0 -1
  147. package/dist/chunk-EANGKAKG.cjs.map +0 -1
  148. package/dist/chunk-EYLBSS4Y.js.map +0 -1
  149. package/dist/chunk-GHKMX5UT.cjs.map +0 -1
  150. package/dist/chunk-KRM6DX47.js.map +0 -1
  151. package/dist/chunk-M3K6BF5M.js.map +0 -1
  152. package/dist/chunk-MCTNLUKO.js.map +0 -1
  153. package/dist/chunk-MPHX3Y42.cjs.map +0 -1
  154. package/dist/chunk-NDIKHMLJ.js.map +0 -1
  155. package/dist/chunk-NILHU555.cjs.map +0 -1
  156. package/dist/chunk-OKEN6NJL.cjs.map +0 -1
  157. package/dist/chunk-PPFVTXGJ.js.map +0 -1
  158. package/dist/chunk-W2P5RILE.cjs.map +0 -1
  159. package/dist/chunk-W6TWQY6S.js.map +0 -1
  160. package/dist/chunk-WGHMWBEJ.cjs.map +0 -1
  161. package/dist/chunk-WNDNQ6NR.js.map +0 -1
  162. package/dist/chunk-WXESKL75.cjs.map +0 -1
  163. package/dist/chunk-XM5Q2ZCH.js.map +0 -1
  164. package/dist/chunk-XZCMUDFP.cjs.map +0 -1
  165. package/dist/chunk-Y4IDF5UO.cjs.map +0 -1
  166. package/dist/chunk-ZDDBWUUA.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,46 @@
1
1
  # @intellectif/lk-react
2
2
 
3
+ ## 23.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - [#82](https://github.com/intellectif/learning-kit/pull/82) [`bcae525`](https://github.com/intellectif/learning-kit/commit/bcae52599d7d0481ccd8ceecdfc59f2bd6d4d281) Thanks [@diego28e](https://github.com/diego28e)! - **1.0**: what every 1.x keeps stable, written down, and from now on a lk-core minor releases no lk-react. No grade changes.
8
+
9
+ **Action required:** only if you read `ScoringDetail.correct`, import a zod schema from lk-core, or register an activity type of your own. Install lk-core 1.0.0 and lk-react 23.0.0 together. Each change and its replacement is in the upgrading guide: https://github.com/intellectif/learning-kit/blob/main/docs/upgrading.md
10
+
11
+ - **Stability:** grades, the public API, content and runtimes — what a 1.x release may and may not change — are set out in https://github.com/intellectif/learning-kit/blob/main/docs/stability.md. lk-react 23 peers on `@intellectif/lk-core@^1.0.0`, and a lk-core minor or patch releases no lk-react.
12
+ - **`ScoringDetail.correct` is removed, and `outcome` is required.** Deprecated since 0.3; on a multiple-choice option it meant "the learner acted rightly on it". Read `outcome`. The grade-stability corpus changed only there: 142 vectors lose `correct`, and no score, pass or feedback moves. A detail stored before 0.3 still carries only `correct`; the SDK's components still read it.
13
+ - **Zod is private to lk-core.** No zod schema is exported and no published type names zod, so your app can use any zod, or none. `validateMedia` and `validateOptionMedia` replace `MediaSchema.safeParse` and `MultipleChoiceOptionMediaSchema.safeParse`; `validateActivity`, `validateDraft` and `validateItemGroup` replace the rest. The `*JsonSchema` constants are typed `Record<string, unknown>`, and the `Redacted*` types are written out, unchanged in shape.
14
+ - **A type you register takes Standard Schemas** (https://standardschema.dev): `schema` and `redactedSchema` are `StandardSchemaV1`. A zod 4 schema is one as written; valibot and ArkType work too. Validation must be synchronous. `registerActivityType` throws on a value that is not a Standard Schema, and `jsonSchemaFor` on your type needs the descriptor's new `jsonSchema` unless the schema produces JSON Schema itself, as zod 4.6 does.
15
+ - **Built on zod 4.6.5, pinned exactly**, instead of the zod 4 preview in zod 3.25. A new zod reaches you only in a lk-core release. If your app uses another version of zod, your bundle carries two copies. Every built-in validation accepts and refuses what it did, checked against 0.21.0 on 28,000 inputs; from 1.0, `vectors/validation.json` in the tarball freezes that for over three thousand inputs, and each release replays it. Two error lists are one error shorter: a speech assessment with a string where a list of phonemes or syllables belongs, and an xAPI statement whose actor's `mbox` is not a `mailto:` address.
16
+ - **Fixed (lk-react):** a host's grade whose details carry no `correct` — every detail lk-core 1.0 writes — had all its details dropped, a read-aloud's word marks among them. It is now read.
17
+
18
+ ### Patch Changes
19
+
20
+ - Updated dependencies [[`bcae525`](https://github.com/intellectif/learning-kit/commit/bcae52599d7d0481ccd8ceecdfc59f2bd6d4d281)]:
21
+ - @intellectif/lk-core@1.0.0
22
+
23
+ ## 22.0.0
24
+
25
+ ### Major Changes
26
+
27
+ - [#80](https://github.com/intellectif/learning-kit/pull/80) [`782b01b`](https://github.com/intellectif/learning-kit/commit/782b01ba697a90583b472ab1a7a2be102fd888c4) Thanks [@diego28e](https://github.com/diego28e)! - **Feedback on writing**: "Get feedback on my draft" on a written response in practice. The learner revises and asks again, and every correction must quote what they wrote.
28
+
29
+ **Action required:** none unless your code switches exhaustively over `AiFeature` or `AiRefusal` (new members `'writing-feedback'` and `'misquotes-answer'`), reads `AiCheckCase.request` without narrowing on `feature`, or implements a complete `LkStrings` (eight new keys). Nothing changes until you pass a `writingFeedback` port. Otherwise this is a major only because the peer range moves to `@intellectif/lk-core@^0.22.0`.
30
+
31
+ - **lk-core:** `aiWritingFeedbackRequest` builds what a model is given: the task, the draft verbatim, its word count as the grader counts it, the rubric, and the feedback on earlier drafts. `checkAiWritingFeedback` checks the reply before a learner sees it. It finds each correction's quote in the draft itself (typographic quotes and runs of whitespace folded, case kept) and anchors it there. A correction of words the draft does not contain refuses the whole reply as `misquotes-answer`. Criteria must be the rubric's; the author's weights are attached, and a model's weight is ignored. The indicative score is `gradeFromRubric`'s total, computed only when every criterion was judged. It is never a grade. Limits: 20 corrections, 500 characters a field.
32
+ - **lk-react:** a `writingFeedback` port and `maxWritingFeedback` (3 by default, at most 10) on `LearnerAi`. `<WrittenResponse>` shows the feedback with its corrections, rubric comments, the indicative score marked "Not a grade." and the AI notice, and says so once the draft has changed. It is never in `exam` or `review`, and it follows the author's and the paper's `ai.explanations`, `feedback` and `solutions`. `useAiWritingFeedback` gives a written response you draw yourself the same rules.
33
+ - **Records:** `ai-writing-feedback-shown` (the draft number, how many corrections, the indicative score, provenance and usage; never the text), and `ai-help-refused` with `feature: 'writing-feedback'`.
34
+ - **ai-check:** a `writingFeedback` port runs four writing cases: a draft with mistakes, one without, a revision, and feedback asked for in Spanish.
35
+ - Eight new strings: `aiWritingFeedback`, `aiWritingFeedbackLoading`, `aiWritingFeedbackHeading`, `aiWritingFeedbackUnavailable`, `aiNoMoreWritingFeedback`, `aiWritingFeedbackOutdated`, `aiCorrections`, `aiIndicativeScore`.
36
+
37
+ Guide: https://github.com/intellectif/learning-kit/blob/main/docs/ai.md#feedback-on-writing
38
+
39
+ ### Patch Changes
40
+
41
+ - Updated dependencies [[`782b01b`](https://github.com/intellectif/learning-kit/commit/782b01ba697a90583b472ab1a7a2be102fd888c4)]:
42
+ - @intellectif/lk-core@0.22.0
43
+
3
44
  ## 21.0.0
4
45
 
5
46
  ### Major Changes
package/README.md CHANGED
@@ -75,7 +75,7 @@ export function Demo() {
75
75
  - **`<InteractiveVideo>`** — a video that stops at each quiz and asks its questions over itself, then plays on. **A formative tool**, for lessons and practice — deliver official summative exams with `<ActivitySequence>`. One player: a progress bar that marks every quiz and previews the caption under the pointer, speed, volume, captions drawn from WebVTT it parses itself (through a `captionsLoader` when the file needs the learner's credentials) — in one language or two at once, a second line under the first for a learner matching what they hear to what it means — a contents and transcript panel, picture-in-picture, fullscreen and a keyboard map with a shortcut sheet. A quiz covers the video and grows the player rather than scrolling inside it, every question of an opened quiz stays mounted so a rewind loses no answer, the controls are `inert` while it is open, and a **required** quiz holds both playback and seeking until it is answered. Answers report with the slot the question sits in and the quiz it belongs to, and `onProgress` gives a resume point that is never past an unanswered required quiz. A host with renderers of its own draws any question itself through `renderQuestion` — the video still counts it, tells it when it leaves the screen so it can stop its microphone, and holds Finish while its answer is still on its way. See the [interactive video guide](https://github.com/intellectif/learning-kit/blob/main/docs/interactive-video.md).
76
76
  - **Delivery policies** — `delivery` on any activity, `<ActivitySequence>` or `<InteractiveVideo>` switches off feedback, solutions, hints or AI help for that paper, so one question serves practice and a final without being authored twice. It can only take away, an empty policy is exactly the default, and a pager's policy holds in every question it holds — a host's own renderer included. See [Delivery policies](https://github.com/intellectif/learning-kit/blob/main/docs/delivery.md).
77
77
  - **Scoring policies** — `scoring` on any activity, `<ActivitySequence>` or `<InteractiveVideo>` gives a practice question "Try again", chooses which try counts, and charges for tries and hints, saying what a hint costs before the learner asks. The right answer and "Explain my answer" wait until the tries end, a set waits for a question being tried again, and no policy changes nothing. See [Scoring policies](https://github.com/intellectif/learning-kit/blob/main/docs/scoring.md).
78
- - **AI help for learners** — "Explain my answer" after grading and "Get a hint" before submit, in `<MultipleChoice>`, `<FillInTheBlanks>`, `<GapSelect>` and `<Dictation>` (explanations only), through ports **you** supply with `<LkAiProvider>` or an `ai` prop (also on `<ActivitySequence>` and `<InteractiveVideo>`). The SDK never calls a model or holds a key: it builds the facts your model is given, refuses an explanation that contradicts the grade and a hint that contains the answer, marks everything a model wrote, and drops a late answer. Never in `exam`. What was shown reaches `onInteraction` with the model that wrote it and what the call cost; what was refused reaches it with the reason, and never the text. See the [AI guide](https://github.com/intellectif/learning-kit/blob/main/docs/ai.md).
78
+ - **AI help for learners** — "Explain my answer" after grading and "Get a hint" before submit, in `<MultipleChoice>`, `<FillInTheBlanks>`, `<GapSelect>` and `<Dictation>` (explanations only), and "Get feedback on my draft" in `<WrittenResponse>` in practice — revise and ask again, and a correction that quotes words the learner never wrote refuses the feedback — through ports **you** supply with `<LkAiProvider>` or an `ai` prop (also on `<ActivitySequence>` and `<InteractiveVideo>`). The SDK never calls a model or holds a key: it builds the facts your model is given, refuses an explanation that contradicts the grade and a hint that contains the answer, marks everything a model wrote, and drops a late answer. Never in `exam`. What was shown reaches `onInteraction` with the model that wrote it and what the call cost; what was refused reaches it with the reason, and never the text. See the [AI guide](https://github.com/intellectif/learning-kit/blob/main/docs/ai.md).
79
79
  - **`<ActivitySequence>`** — in-place "question set" pager (Previous/Next, "Question X of N", no scrolling, focus-managed). Accepts item groups and keeps their stimulus beside every question; `onSubmit` reports every raw answer with its `slotId`, which is the only response channel an `exam` sequence has. `onFinished` is the completion signal for a set that mixes scored and deferred-graded items, where `onComplete` can never fire. A read-aloud slot takes its storage from `recordingBinding`, whose methods are told which slot a take belongs to, and a `review` its per-word marks from `assessments`, keyed by `slotId` — there is a [worked example](https://github.com/intellectif/learning-kit/blob/main/docs/speech-assessment.md#in-a-question-set).
80
80
  - **Resume and review a whole attempt** — `<ActivitySequence>` takes `defaultIndex`, `responses` and `submittedSlotIds` (read at mount; remount with a `key` to show a different attempt), so an interrupted paper reopens on the right question with the right answers and already-committed questions still committed. `onIndexChange` reports every position the pager lands on, including a clamp it had to apply. Pass `renderMode="review"` plus server-computed `outcomes` to render a finished attempt read-only. Pairs with `serializeAttemptState` / `restoreAttemptState` in `lk-core`.
81
81
  - **`renderMode`** — `practice` (default: the component scores locally and reveals correctness), `exam` (never scores, never reveals; submit emits the raw response for the server to grade), `review` (read-only, marks correctness only from an `outcome` you supply). This is the single switch that takes grading off the client.
@@ -85,7 +85,7 @@ export function Demo() {
85
85
  - **`<StimulusPanel>`** — a shared passage / recording / image (an item group's stimulus) as a landmark region; the sequence uses it, and a custom runner can too.
86
86
  - **`<ActivityPreview>`** — an editor's preview. Renders a draft in any `renderMode` with a simulated `response` (marked with `evaluate()` in `review`), and a translatable notice — or your `fallback`, handed the issues — while `validateDraft` says the draft is not finished. Nothing it renders is recorded, and a recording's play limit is counted in memory only.
87
87
  - **Custom activity types** — `renderers={{ 'my-type': MyRenderer }}` on the sequence; a key matching a built-in overrides it, so you can replace a bundled renderer without forking the sequencer. A renderer is given the same contract the SDK's own components have: a `question` prop that says when it is the question on screen (so it can stop its microphone), holds the set while its own work is in flight, gives it somewhere to portal a popover, and reports what the learner did through calls that keep one identity.
88
- - **AI help in a question you draw** — `useAiHints` and `useAiExplanation` give a host's own renderer the rules, the facts and the refusals the bundled components use, without their buttons. `ask` does nothing where the rules say no, so an exam reaches no model however the page asks.
88
+ - **AI help in a question you draw** — `useAiHints`, `useAiExplanation` and `useAiWritingFeedback` give a host's own renderer the rules, the facts and the refusals the bundled components use, without their buttons. `ask` does nothing where the rules say no, so an exam reaches no model however the page asks.
89
89
  - **Rich text, opt-in** — `sanitizeHtml` renders author-supplied `questionHtml` / `promptHtml`. The SDK ships **no** sanitiser and injects no HTML without one; without it the escaped plain-text field is used. (`FillInTheBlanks` ignores `passageHtml` by design — its passage hosts the answer inputs.)
90
90
  - **Media per question** — optional `image` / `audio` / `video` / `embed` (YouTube/Vimeo iframe) above the question; alt-text required for `image`/`embed` (WCAG).
91
91
  - **Playback policy for listening papers** — an audio recording can declare `maxPlays`, `seek: 'none'` and `rate: 'fixed'`. The SDK then renders its own accessible transport (44px targets, a live plays-remaining status, an exhausted button that stays focusable and says why) and enforces the policy from the element's own events, so a hardware media key goes through the budget too. Wire `mediaBudget` on `<ActivitySequence>` to make a play budget survive a refresh.
@@ -95,7 +95,7 @@ export function Demo() {
95
95
  - **`useSpeechRecorder(options)`** — the capture half of a read-aloud, usable on its own: microphone permission, an RMS level for a meter, a minimum take length, and an auto-stop measured from the **sample count** rather than a timer, so a take can never outrun its own limit. It yields **16 kHz mono 16-bit PCM WAV** — the one format `inspectWav` in `lk-core` measures without a decoder. SSR-safe by construction (support is discovered inside `start()`, never probed during render, so a server render and its hydration cannot disagree), and the microphone and the audio context are released on stop, discard, error and unmount alike. Where a Content-Security-Policy's `script-src` does not allow `blob:`, serve `CAPTURE_PROCESSOR_SOURCE` — exported beside the hook, with the contract it meets written on it — and pass its URL as `workletUrl`. A `workletUrl` that fails to load is not reported: the hook falls back to the main-thread `ScriptProcessorNode`, so check that the module is fetched.
96
96
  - **`<ThemeProvider>` + `defaults.css`** — `--lk-*` design-token system; automatic dark mode via `prefers-color-scheme` (SSR-safe with `useSyncExternalStore`).
97
97
  - **`createTailwindTheme(theme)`** — optional Tailwind interop; consume the SDK palette from your own utilities.
98
- - **`<LkIntlProvider>`** — all 188 strings the SDK's own chrome renders, replaceable in one place, with a per-component `strings` prop for the exceptions. Interpolation and plurals are **functions**, not format strings, so your `Intl.PluralRules` does the work and TypeScript checks the arity. `locale` sets `lang` and derives `dir`; the skin uses logical properties, so RTL follows. The SDK ships the mechanism and **English only** — see [docs/i18n.md](https://github.com/intellectif/learning-kit/blob/main/docs/i18n.md).
98
+ - **`<LkIntlProvider>`** — all 196 strings the SDK's own chrome renders, replaceable in one place, with a per-component `strings` prop for the exceptions. Interpolation and plurals are **functions**, not format strings, so your `Intl.PluralRules` does the work and TypeScript checks the arity. `locale` sets `lang` and derives `dir`; the skin uses logical properties, so RTL follows. The SDK ships the mechanism and **English only** — see [docs/i18n.md](https://github.com/intellectif/learning-kit/blob/main/docs/i18n.md).
99
99
  - **WCAG 2.2 AA** — axe-clean unit + Playwright e2e tests; full keyboard operability; numerically-verified contrast.
100
100
  - **RSC-compatible** — every component carries `'use client'` and hydrates inside a React Server Component tree.
101
101
 
@@ -119,7 +119,7 @@ export function Demo() {
119
119
  | `@intellectif/lk-react/hooks/useSpeechRecorder` | Microphone capture to 16 kHz mono WAV, and `CAPTURE_PROCESSOR_SOURCE` for a self-hosted worklet |
120
120
  | `@intellectif/lk-react/hooks/useXAPI` | LRS delivery (retry, never-throws) |
121
121
  | `@intellectif/lk-react/ai/LkAiProvider` | `<LkAiProvider>`, `useLearnerAi`, and the `LearnerAi` type |
122
- | `@intellectif/lk-react/ai/useAiHelp` | `useAiHints`, `useAiExplanation` — the AI rules without the SDK's own buttons |
122
+ | `@intellectif/lk-react/ai/useAiHelp` | `useAiHints`, `useAiExplanation`, `useAiWritingFeedback` — the AI rules without the SDK's own buttons |
123
123
  | `@intellectif/lk-react/i18n/LkIntlProvider` | `<LkIntlProvider>`, `useLkStrings`, `useLkDirection`, `DEFAULT_STRINGS`, `mergeStrings`, `directionForLocale` |
124
124
  | `@intellectif/lk-react/theme/ThemeProvider` | `<ThemeProvider>`, `darkTheme`, `useTheme`, `createTailwindTheme` |
125
125
  | `@intellectif/lk-react/theme/defaults.css` | Tokens (required) |
@@ -1,5 +1,6 @@
1
1
  import { XAPIStatement, WrittenResponseData, LearnerResponse, ItemOutcome, InteractionEvent, ThemeTokens, DeliveryPolicy } from '@intellectif/lk-core';
2
- import { f as Renderable, e as RenderMode, H as HtmlSanitizer, M as MediaBudgetBinding, b as MediaTransportStrings, a as LkStringsOverride } from './types-Botf4Wqj.cjs';
2
+ import { LearnerAi } from './ai/LkAiProvider.js';
3
+ import { f as Renderable, e as RenderMode, H as HtmlSanitizer, M as MediaBudgetBinding, b as MediaTransportStrings, a as LkStringsOverride } from './types-CrtIcRcK.js';
3
4
 
4
5
  /**
5
6
  * Payload delivered when the learner submits a written response. Grading is
@@ -95,11 +96,20 @@ interface WrittenResponseProps {
95
96
  locale?: string;
96
97
  disabled?: boolean;
97
98
  /**
98
- * The delivery policy. An essay is graded later, so only `feedback` reaches
99
- * it: `false` reads a returned grade back as nothing in `review`. See
100
- * `DeliveryPolicy` in lk-core.
99
+ * The delivery policy. An essay is graded later, so `feedback` reaches it —
100
+ * `false` reads a returned grade back as nothing in `review` — and so does
101
+ * AI feedback on a draft, which needs `feedback`, `solutions` and
102
+ * `ai.explanations`. See `DeliveryPolicy` in lk-core.
101
103
  */
102
104
  delivery?: DeliveryPolicy | null;
105
+ /**
106
+ * The host's AI ports, overriding `LkAiProvider`'s. With a `writingFeedback`
107
+ * port, a learner in `practice` can ask for feedback on a draft before
108
+ * submitting it — up to `maxWritingFeedback` times — and revise. Every
109
+ * correction in it quotes the learner's own words, or the SDK refuses it.
110
+ * Never in `exam` or `review`. See {@link LearnerAi}.
111
+ */
112
+ ai?: LearnerAi;
103
113
  }
104
114
 
105
115
  export type { WrittenResponseProps as W, WrittenResponseSubmission as a };
@@ -1,5 +1,6 @@
1
1
  import { XAPIStatement, WrittenResponseData, LearnerResponse, ItemOutcome, InteractionEvent, ThemeTokens, DeliveryPolicy } from '@intellectif/lk-core';
2
- import { f as Renderable, e as RenderMode, H as HtmlSanitizer, M as MediaBudgetBinding, b as MediaTransportStrings, a as LkStringsOverride } from './types-7lGlo0zE.js';
2
+ import { LearnerAi } from './ai/LkAiProvider.cjs';
3
+ import { f as Renderable, e as RenderMode, H as HtmlSanitizer, M as MediaBudgetBinding, b as MediaTransportStrings, a as LkStringsOverride } from './types-CO0XLEN6.cjs';
3
4
 
4
5
  /**
5
6
  * Payload delivered when the learner submits a written response. Grading is
@@ -95,11 +96,20 @@ interface WrittenResponseProps {
95
96
  locale?: string;
96
97
  disabled?: boolean;
97
98
  /**
98
- * The delivery policy. An essay is graded later, so only `feedback` reaches
99
- * it: `false` reads a returned grade back as nothing in `review`. See
100
- * `DeliveryPolicy` in lk-core.
99
+ * The delivery policy. An essay is graded later, so `feedback` reaches it —
100
+ * `false` reads a returned grade back as nothing in `review` — and so does
101
+ * AI feedback on a draft, which needs `feedback`, `solutions` and
102
+ * `ai.explanations`. See `DeliveryPolicy` in lk-core.
101
103
  */
102
104
  delivery?: DeliveryPolicy | null;
105
+ /**
106
+ * The host's AI ports, overriding `LkAiProvider`'s. With a `writingFeedback`
107
+ * port, a learner in `practice` can ask for feedback on a draft before
108
+ * submitting it — up to `maxWritingFeedback` times — and revise. Every
109
+ * correction in it quotes the learner's own words, or the SDK refuses it.
110
+ * Never in `exam` or `review`. See {@link LearnerAi}.
111
+ */
112
+ ai?: LearnerAi;
103
113
  }
104
114
 
105
115
  export type { WrittenResponseProps as W, WrittenResponseSubmission as a };
@@ -1,17 +1,17 @@
1
1
  'use client';
2
2
  'use strict';
3
3
 
4
- var chunkMPHX3Y42_cjs = require('../chunk-MPHX3Y42.cjs');
4
+ var chunkOJGTJOWI_cjs = require('../chunk-OJGTJOWI.cjs');
5
5
 
6
6
 
7
7
 
8
8
  Object.defineProperty(exports, "LkAiProvider", {
9
9
  enumerable: true,
10
- get: function () { return chunkMPHX3Y42_cjs.LkAiProvider; }
10
+ get: function () { return chunkOJGTJOWI_cjs.LkAiProvider; }
11
11
  });
12
12
  Object.defineProperty(exports, "useLearnerAi", {
13
13
  enumerable: true,
14
- get: function () { return chunkMPHX3Y42_cjs.useLearnerAi; }
14
+ get: function () { return chunkOJGTJOWI_cjs.useLearnerAi; }
15
15
  });
16
16
  //# sourceMappingURL=LkAiProvider.cjs.map
17
17
  //# sourceMappingURL=LkAiProvider.cjs.map
@@ -1,4 +1,4 @@
1
- import { AiExplanationRequest, AiTextResult, AiHintRequest } from '@intellectif/lk-core';
1
+ import { AiExplanationRequest, AiTextResult, AiHintRequest, AiWritingFeedbackRequest, AiWritingFeedbackResult } from '@intellectif/lk-core';
2
2
  import { ReactNode } from 'react';
3
3
 
4
4
  /**
@@ -28,6 +28,17 @@ interface LearnerAi {
28
28
  }) => Promise<AiTextResult>;
29
29
  /** The most hints a learner can ask for on one question. Default 3, at most 10. */
30
30
  maxHints?: number;
31
+ /**
32
+ * Gives feedback on a draft of a written response, before the learner
33
+ * submits it: the overall feedback as text, corrections that quote the
34
+ * draft, and a judgement per rubric criterion. See `checkAiWritingFeedback`
35
+ * in lk-core for what is refused.
36
+ */
37
+ writingFeedback?: (request: AiWritingFeedbackRequest, options: {
38
+ signal: AbortSignal;
39
+ }) => Promise<AiWritingFeedbackResult>;
40
+ /** How many times a learner can ask for feedback on one written response. Default 3, at most 10. */
41
+ maxWritingFeedback?: number;
31
42
  /**
32
43
  * The language to write explanations and hints in, as a tag (`es`,
33
44
  * `pt-BR`). Defaults to the component's `locale`: the interface language,
@@ -1,4 +1,4 @@
1
- import { AiExplanationRequest, AiTextResult, AiHintRequest } from '@intellectif/lk-core';
1
+ import { AiExplanationRequest, AiTextResult, AiHintRequest, AiWritingFeedbackRequest, AiWritingFeedbackResult } from '@intellectif/lk-core';
2
2
  import { ReactNode } from 'react';
3
3
 
4
4
  /**
@@ -28,6 +28,17 @@ interface LearnerAi {
28
28
  }) => Promise<AiTextResult>;
29
29
  /** The most hints a learner can ask for on one question. Default 3, at most 10. */
30
30
  maxHints?: number;
31
+ /**
32
+ * Gives feedback on a draft of a written response, before the learner
33
+ * submits it: the overall feedback as text, corrections that quote the
34
+ * draft, and a judgement per rubric criterion. See `checkAiWritingFeedback`
35
+ * in lk-core for what is refused.
36
+ */
37
+ writingFeedback?: (request: AiWritingFeedbackRequest, options: {
38
+ signal: AbortSignal;
39
+ }) => Promise<AiWritingFeedbackResult>;
40
+ /** How many times a learner can ask for feedback on one written response. Default 3, at most 10. */
41
+ maxWritingFeedback?: number;
31
42
  /**
32
43
  * The language to write explanations and hints in, as a tag (`es`,
33
44
  * `pt-BR`). Defaults to the component's `locale`: the interface language,
@@ -1,4 +1,4 @@
1
1
  'use client';
2
- export { LkAiProvider, useLearnerAi } from '../chunk-MCTNLUKO.js';
2
+ export { LkAiProvider, useLearnerAi } from '../chunk-RX3A6KIY.js';
3
3
  //# sourceMappingURL=LkAiProvider.js.map
4
4
  //# sourceMappingURL=LkAiProvider.js.map
@@ -1,20 +1,24 @@
1
1
  'use client';
2
2
  'use strict';
3
3
 
4
- var chunkY4IDF5UO_cjs = require('../chunk-Y4IDF5UO.cjs');
5
- require('../chunk-MPHX3Y42.cjs');
4
+ var chunk6RIM5EQP_cjs = require('../chunk-6RIM5EQP.cjs');
5
+ require('../chunk-OJGTJOWI.cjs');
6
6
  require('../chunk-J2QIHZ3H.cjs');
7
- require('../chunk-EANGKAKG.cjs');
7
+ require('../chunk-54DX6SLS.cjs');
8
8
 
9
9
 
10
10
 
11
11
  Object.defineProperty(exports, "useAiExplanation", {
12
12
  enumerable: true,
13
- get: function () { return chunkY4IDF5UO_cjs.useAiExplanation; }
13
+ get: function () { return chunk6RIM5EQP_cjs.useAiExplanation; }
14
14
  });
15
15
  Object.defineProperty(exports, "useAiHints", {
16
16
  enumerable: true,
17
- get: function () { return chunkY4IDF5UO_cjs.useAiHints; }
17
+ get: function () { return chunk6RIM5EQP_cjs.useAiHints; }
18
+ });
19
+ Object.defineProperty(exports, "useAiWritingFeedback", {
20
+ enumerable: true,
21
+ get: function () { return chunk6RIM5EQP_cjs.useAiWritingFeedback; }
18
22
  });
19
23
  //# sourceMappingURL=useAiHelp.cjs.map
20
24
  //# sourceMappingURL=useAiHelp.cjs.map
@@ -1,5 +1,5 @@
1
- import { AiTextResult, LearnerResponse, InteractionEvent, DeliveryPolicy, ItemOutcome } from '@intellectif/lk-core';
2
- import { g as RenderableActivity, e as RenderMode } from '../types-Botf4Wqj.cjs';
1
+ import { AiTextResult, LearnerResponse, InteractionEvent, DeliveryPolicy, ItemOutcome, AiWritingFeedback } from '@intellectif/lk-core';
2
+ import { g as RenderableActivity, e as RenderMode } from '../types-CO0XLEN6.cjs';
3
3
  import { LearnerAi } from './LkAiProvider.cjs';
4
4
  import '../machine-CuwKqWXC.cjs';
5
5
  import 'react';
@@ -148,5 +148,60 @@ declare function useAiExplanation(input: AiExplanationInput): AiExplanationHelp;
148
148
  * given — while `offered` turns false. Another question starts with none.
149
149
  */
150
150
  declare function useAiHints(input: AiHintsInput): AiHintsHelp;
151
+ interface AiWritingFeedbackInput extends AiHelpSituation {
152
+ /** A disabled question takes no feedback, as it takes no answer. */
153
+ disabled?: boolean;
154
+ }
155
+ interface AiWritingFeedbackHelp {
156
+ /**
157
+ * Whether the learner may ask for feedback here at all: a port is present,
158
+ * the mode is `practice`, the answer is not in, the question is a written
159
+ * response that is not disabled or redacted, its author left explanations on,
160
+ * and the paper shows feedback and solutions and allows AI explanations. It
161
+ * stays true once `used` reaches `limit`.
162
+ */
163
+ offered: boolean;
164
+ status: 'idle' | 'loading' | 'unavailable';
165
+ /** Every piece of feedback shown for this question, oldest first. */
166
+ feedback: readonly AiWritingFeedback[];
167
+ /** The latest, or `null` before any. Its `text` is text, never HTML. */
168
+ latest: AiWritingFeedback | null;
169
+ /**
170
+ * Whether `latest` is about the draft as it stands. Once the learner revises,
171
+ * its corrections point at words that may no longer be there: say so.
172
+ */
173
+ current: boolean;
174
+ /** The host's `maxWritingFeedback`, as a whole number from 1 to 10 (3 by default). */
175
+ limit: number;
176
+ /** How many times feedback has been shown. A refused reply is not counted. */
177
+ used: number;
178
+ /**
179
+ * Asks for feedback on the draft as it stands. Does nothing at the limit,
180
+ * while a call is on its way, on an empty draft, or where `offered` is false.
181
+ * One identity for the life of the question.
182
+ */
183
+ ask: () => void;
184
+ }
185
+ /**
186
+ * "Get feedback on my draft", without the SDK's own button and panel: the
187
+ * rules, the call, the checks and the record, for a written response you draw
188
+ * yourself.
189
+ *
190
+ * It is what `<WrittenResponse>` uses, so a host's own essay box gets feedback
191
+ * on the same terms: only in `practice` before submit, up to the host's limit,
192
+ * and never feedback that corrects words the learner did not write — which is
193
+ * refused rather than shown, and not counted.
194
+ *
195
+ * ```tsx
196
+ * const { offered, status, latest, current, used, limit, ask } = useAiWritingFeedback({
197
+ * data, response, submitted, renderMode, ai: question.ai,
198
+ * });
199
+ * ```
200
+ *
201
+ * **Say who wrote it**, as the SDK's own panel does: "Written by AI. It can make
202
+ * mistakes." And show `indicativeScore` as what it is — an indication, not a
203
+ * grade.
204
+ */
205
+ declare function useAiWritingFeedback(input: AiWritingFeedbackInput): AiWritingFeedbackHelp;
151
206
 
152
- export { type AiExplanationHelp, type AiExplanationInput, type AiHelpSituation, type AiHintsHelp, type AiHintsInput, useAiExplanation, useAiHints };
207
+ export { type AiExplanationHelp, type AiExplanationInput, type AiHelpSituation, type AiHintsHelp, type AiHintsInput, type AiWritingFeedbackHelp, type AiWritingFeedbackInput, useAiExplanation, useAiHints, useAiWritingFeedback };
@@ -1,5 +1,5 @@
1
- import { AiTextResult, LearnerResponse, InteractionEvent, DeliveryPolicy, ItemOutcome } from '@intellectif/lk-core';
2
- import { g as RenderableActivity, e as RenderMode } from '../types-7lGlo0zE.js';
1
+ import { AiTextResult, LearnerResponse, InteractionEvent, DeliveryPolicy, ItemOutcome, AiWritingFeedback } from '@intellectif/lk-core';
2
+ import { g as RenderableActivity, e as RenderMode } from '../types-CrtIcRcK.js';
3
3
  import { LearnerAi } from './LkAiProvider.js';
4
4
  import '../machine-CuwKqWXC.js';
5
5
  import 'react';
@@ -148,5 +148,60 @@ declare function useAiExplanation(input: AiExplanationInput): AiExplanationHelp;
148
148
  * given — while `offered` turns false. Another question starts with none.
149
149
  */
150
150
  declare function useAiHints(input: AiHintsInput): AiHintsHelp;
151
+ interface AiWritingFeedbackInput extends AiHelpSituation {
152
+ /** A disabled question takes no feedback, as it takes no answer. */
153
+ disabled?: boolean;
154
+ }
155
+ interface AiWritingFeedbackHelp {
156
+ /**
157
+ * Whether the learner may ask for feedback here at all: a port is present,
158
+ * the mode is `practice`, the answer is not in, the question is a written
159
+ * response that is not disabled or redacted, its author left explanations on,
160
+ * and the paper shows feedback and solutions and allows AI explanations. It
161
+ * stays true once `used` reaches `limit`.
162
+ */
163
+ offered: boolean;
164
+ status: 'idle' | 'loading' | 'unavailable';
165
+ /** Every piece of feedback shown for this question, oldest first. */
166
+ feedback: readonly AiWritingFeedback[];
167
+ /** The latest, or `null` before any. Its `text` is text, never HTML. */
168
+ latest: AiWritingFeedback | null;
169
+ /**
170
+ * Whether `latest` is about the draft as it stands. Once the learner revises,
171
+ * its corrections point at words that may no longer be there: say so.
172
+ */
173
+ current: boolean;
174
+ /** The host's `maxWritingFeedback`, as a whole number from 1 to 10 (3 by default). */
175
+ limit: number;
176
+ /** How many times feedback has been shown. A refused reply is not counted. */
177
+ used: number;
178
+ /**
179
+ * Asks for feedback on the draft as it stands. Does nothing at the limit,
180
+ * while a call is on its way, on an empty draft, or where `offered` is false.
181
+ * One identity for the life of the question.
182
+ */
183
+ ask: () => void;
184
+ }
185
+ /**
186
+ * "Get feedback on my draft", without the SDK's own button and panel: the
187
+ * rules, the call, the checks and the record, for a written response you draw
188
+ * yourself.
189
+ *
190
+ * It is what `<WrittenResponse>` uses, so a host's own essay box gets feedback
191
+ * on the same terms: only in `practice` before submit, up to the host's limit,
192
+ * and never feedback that corrects words the learner did not write — which is
193
+ * refused rather than shown, and not counted.
194
+ *
195
+ * ```tsx
196
+ * const { offered, status, latest, current, used, limit, ask } = useAiWritingFeedback({
197
+ * data, response, submitted, renderMode, ai: question.ai,
198
+ * });
199
+ * ```
200
+ *
201
+ * **Say who wrote it**, as the SDK's own panel does: "Written by AI. It can make
202
+ * mistakes." And show `indicativeScore` as what it is — an indication, not a
203
+ * grade.
204
+ */
205
+ declare function useAiWritingFeedback(input: AiWritingFeedbackInput): AiWritingFeedbackHelp;
151
206
 
152
- export { type AiExplanationHelp, type AiExplanationInput, type AiHelpSituation, type AiHintsHelp, type AiHintsInput, useAiExplanation, useAiHints };
207
+ export { type AiExplanationHelp, type AiExplanationInput, type AiHelpSituation, type AiHintsHelp, type AiHintsInput, type AiWritingFeedbackHelp, type AiWritingFeedbackInput, useAiExplanation, useAiHints, useAiWritingFeedback };
@@ -1,7 +1,7 @@
1
1
  'use client';
2
- export { useAiExplanation, useAiHints } from '../chunk-NDIKHMLJ.js';
3
- import '../chunk-MCTNLUKO.js';
2
+ export { useAiExplanation, useAiHints, useAiWritingFeedback } from '../chunk-CXIP7ERK.js';
3
+ import '../chunk-RX3A6KIY.js';
4
4
  import '../chunk-U4KTOH7C.js';
5
- import '../chunk-PPFVTXGJ.js';
5
+ import '../chunk-DXFCYNST.js';
6
6
  //# sourceMappingURL=useAiHelp.js.map
7
7
  //# sourceMappingURL=useAiHelp.js.map
@@ -1,8 +1,8 @@
1
1
  'use client';
2
2
  import { localeDirectionOf } from './chunk-YORQQXGO.js';
3
- import { ActivityErrorBoundary } from './chunk-U6SEII5V.js';
4
- import { isDevelopment } from './chunk-PPFVTXGJ.js';
5
- import { useLkStrings } from './chunk-EYLBSS4Y.js';
3
+ import { ActivityErrorBoundary } from './chunk-E4UF4XZM.js';
4
+ import { isDevelopment } from './chunk-DXFCYNST.js';
5
+ import { useLkStrings } from './chunk-C4PSJ3ZC.js';
6
6
  import { ActivitySchemaError, alignReadAloud, validateSpeechAssessment } from '@intellectif/lk-core';
7
7
  import { useState, useCallback, useId, useMemo, useRef, useEffect } from 'react';
8
8
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
@@ -90,13 +90,19 @@ function readDetail(value) {
90
90
  return null;
91
91
  }
92
92
  const { itemId, correct, outcome, learnerResponse, correctResponse, weight, score } = value;
93
- if (typeof itemId !== "string" || typeof correct !== "boolean" || !isResponse(learnerResponse) || !isResponse(correctResponse)) {
93
+ if (typeof itemId !== "string" || !isResponse(learnerResponse) || !isResponse(correctResponse)) {
94
94
  return null;
95
95
  }
96
96
  const checkedOutcome = OUTCOMES.find((known) => known === outcome);
97
97
  if (!isAbsent(outcome) && checkedOutcome === void 0) {
98
98
  return null;
99
99
  }
100
+ if (!isAbsent(correct) && typeof correct !== "boolean") {
101
+ return null;
102
+ }
103
+ if (checkedOutcome === void 0 && typeof correct !== "boolean") {
104
+ return null;
105
+ }
100
106
  if (!isAbsent(weight) && !isFiniteNumber(weight)) {
101
107
  return null;
102
108
  }
@@ -106,10 +112,9 @@ function readDetail(value) {
106
112
  }
107
113
  return {
108
114
  itemId,
109
- correct,
110
115
  learnerResponse,
111
116
  correctResponse,
112
- ...checkedOutcome !== void 0 ? { outcome: checkedOutcome } : {},
117
+ ...checkedOutcome !== void 0 ? { outcome: checkedOutcome } : { correct },
113
118
  ...!isAbsent(weight) ? { weight } : {},
114
119
  ...checkedScore !== void 0 ? { score: checkedScore } : {}
115
120
  };
@@ -534,5 +539,5 @@ function PronunciationFeedback2(props) {
534
539
  }
535
540
 
536
541
  export { PronunciationFeedback2 as PronunciationFeedback, VISUALLY_HIDDEN, fractionOfGrade, markSentence, percentOfGrade, readAssessResult, readEvidence, readOutcome, usePlaybackRefusal };
537
- //# sourceMappingURL=chunk-M3K6BF5M.js.map
538
- //# sourceMappingURL=chunk-M3K6BF5M.js.map
542
+ //# sourceMappingURL=chunk-2OP5YHRR.js.map
543
+ //# sourceMappingURL=chunk-2OP5YHRR.js.map