From 93e81fce36c98d820c5ccbade943fc927fa9ba4c Mon Sep 17 00:00:00 2001 From: Ariel Harlap <3870784+aharlap@users.noreply.github.com> Date: Mon, 19 Oct 2026 21:08:30 -0400 Subject: [PATCH 1/3] Check composer guidance against pinned library READMEs Co-Authored-By: GPT-6.1 Sol --- .github/workflows/ci.yml | 1 + README.md | 10 + docs/provenance/learning-patterns.json | 22 +++ docs/provenance/learning-patterns/LICENSE | 21 +++ .../patterns/conversation/README.md | 108 +++++++++++ .../patterns/course-lookup/README.md | 128 +++++++++++++ .../patterns/dont-know/README.md | 172 ++++++++++++++++++ .../patterns/explain-back/README.md | 104 +++++++++++ .../patterns/first-answer/README.md | 122 +++++++++++++ .../patterns/highlight/README.md | 117 ++++++++++++ .../patterns/journal/README.md | 113 ++++++++++++ .../patterns/live-feedback/README.md | 113 ++++++++++++ .../patterns/misconception/README.md | 103 +++++++++++ .../patterns/retrieval-sheet/README.md | 172 ++++++++++++++++++ .../patterns/review-prompts/README.md | 124 +++++++++++++ .../patterns/self-check/README.md | 114 ++++++++++++ .../patterns/test-out/README.md | 118 ++++++++++++ .../patterns/write-distractors/README.md | 131 +++++++++++++ package.json | 1 + scripts/check-composer-patterns.mjs | 93 ++++++++++ skills/composer/patterns.md | 66 ++++--- test/composer-patterns.test.mjs | 162 +++++++++++++++++ test/golden/adapters.json | 4 +- 23 files changed, 2096 insertions(+), 23 deletions(-) create mode 100644 docs/provenance/learning-patterns.json create mode 100644 docs/provenance/learning-patterns/LICENSE create mode 100644 docs/provenance/learning-patterns/patterns/conversation/README.md create mode 100644 docs/provenance/learning-patterns/patterns/course-lookup/README.md create mode 100644 docs/provenance/learning-patterns/patterns/dont-know/README.md create mode 100644 docs/provenance/learning-patterns/patterns/explain-back/README.md create mode 100644 docs/provenance/learning-patterns/patterns/first-answer/README.md create mode 100644 docs/provenance/learning-patterns/patterns/highlight/README.md create mode 100644 docs/provenance/learning-patterns/patterns/journal/README.md create mode 100644 docs/provenance/learning-patterns/patterns/live-feedback/README.md create mode 100644 docs/provenance/learning-patterns/patterns/misconception/README.md create mode 100644 docs/provenance/learning-patterns/patterns/retrieval-sheet/README.md create mode 100644 docs/provenance/learning-patterns/patterns/review-prompts/README.md create mode 100644 docs/provenance/learning-patterns/patterns/self-check/README.md create mode 100644 docs/provenance/learning-patterns/patterns/test-out/README.md create mode 100644 docs/provenance/learning-patterns/patterns/write-distractors/README.md create mode 100644 scripts/check-composer-patterns.mjs create mode 100644 test/composer-patterns.test.mjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 11efc85..7856c6c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,6 +51,7 @@ jobs: with: node-version: 24.21.0 - run: npm run validate + - run: npm run check:composer-patterns - run: node --test - run: npm run validate:release macos-install: diff --git a/README.md b/README.md index 4d80680..d4ee528 100644 --- a/README.md +++ b/README.md @@ -477,6 +477,16 @@ Typst 0.15.1 hashes came from [official release asset metadata](https://api.gith `docs/provenance/` preserves the signed Node list, public key and extracted checksums. Neither origin settles signing, Gatekeeper or clean-Mac acceptance. +The composer's library guidance uses a checked-in copy of the 14 learning-patterns +READMEs and their MIT licence under `docs/provenance/learning-patterns/`. +`docs/provenance/learning-patterns.json` records the full source revision, source +URL and SHA-256 of each copied file. `npm run check:composer-patterns` verifies +those hashes and compares the use/avoid sections with `skills/composer/patterns.md` +without a network connection. CI runs this check. To update the source, copy the +READMEs and licence from a reviewed revision, update the provenance and hashes, +then run `npm run check:composer-patterns -- --write` and review the generated +diff. Keep Studio support and composition guidance outside the generated region. + Rubato's own scripts, skills and adapters are MIT-licensed (see `LICENSE`). The tools it installs keep their own licences: Studio, Check, Trace, Print and Import are under PolyForm Perimeter 1.0.1, with their third-party notices inside diff --git a/docs/provenance/learning-patterns.json b/docs/provenance/learning-patterns.json new file mode 100644 index 0000000..c189ac1 --- /dev/null +++ b/docs/provenance/learning-patterns.json @@ -0,0 +1,22 @@ +{ + "source": "https://github.com/Praxity/learning-patterns/tree/85f03f1652ff756579e82e1a386345ef28ecd894", + "sourceRevision": "85f03f1652ff756579e82e1a386345ef28ecd894", + "license": "MIT", + "files": { + "LICENSE": "d817c04f10efaffd1ab6dba0792a6567dce98f8ab41e86c64833a7b9cff7ecc5", + "patterns/conversation/README.md": "e7905b5f6428f051bd713925ab62d730c4439eb9f83156a7129081945109ed21", + "patterns/course-lookup/README.md": "8d998c9f4acf83e4d2e46c31e36737f4c06b3e4fda9be9bd1919aa665a94b8a7", + "patterns/dont-know/README.md": "7426f50a790f4d493520ffd08368592e76217b8de8ae091246a54ab68c743b5e", + "patterns/explain-back/README.md": "2de4bf358b88d386a07e6d9dd2ca79e11757e331e2eb6a6fa192b0c1a0b16f0f", + "patterns/first-answer/README.md": "f17127befa84657cee5b1a853aefbb5bd962c2c1a0063f14e7daec1e361f0fa9", + "patterns/highlight/README.md": "4f7d7615e079b92196b5cbb0c0f2c296683490d60db6e64fefaaa75a793ed91d", + "patterns/journal/README.md": "ec0c57adfd400063640f4bb1b324ff3eaf8d24052313a765b97afab1569dbadf", + "patterns/live-feedback/README.md": "9dfbe57b30a9525e6ce10912952d51cc45d3896686822d8caa291a23940c9944", + "patterns/misconception/README.md": "26e45cdc9bbc70582571ebb3ab391db945848b9b9544a6e9f85a7aafebff2d17", + "patterns/retrieval-sheet/README.md": "30b017eb6121534538cdedf934d943b2e57cb4f058113367aadab03e8718aee8", + "patterns/review-prompts/README.md": "f53cbe5f287d64010f7a71209ca0172218c1cd009f2a85b34bd0370fcbe0de70", + "patterns/self-check/README.md": "b287cbc682d6aeb2d6788574cb9454e6ebd7f9f00247931a2d07d7a17f4a9614", + "patterns/test-out/README.md": "86962152f36d93f0ce765b999d57523b790bb8968953d2cd983c2c0f49eacc85", + "patterns/write-distractors/README.md": "91ccba1707d1a2b09ac3a78c55a57262411f6a8d6441c2d08276f039313488e2" + } +} diff --git a/docs/provenance/learning-patterns/LICENSE b/docs/provenance/learning-patterns/LICENSE new file mode 100644 index 0000000..987291f --- /dev/null +++ b/docs/provenance/learning-patterns/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Ariel Harlap + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/docs/provenance/learning-patterns/patterns/conversation/README.md b/docs/provenance/learning-patterns/patterns/conversation/README.md new file mode 100644 index 0000000..32e1fab --- /dev/null +++ b/docs/provenance/learning-patterns/patterns/conversation/README.md @@ -0,0 +1,108 @@ +--- +title: Scenario talk-through +title_fr: Mise en pratique dans un scénario +summary: Practise two replies to an upset colleague and review what happened. +section: conversation +ai: yes +offline: yes +learners: not tried +--- +# Scenario talk-through + +You changed a work plan without asking Michel. Practise answering him in two rounds. + +## Use it when + +Use it to practise a difficult workplace conversation. You can acknowledge the impact, defend the change, attack, withdraw or ask for a pause. Michel responds to each choice. The review explains the effects of your two replies. + +## Avoid it when + +It gives no score. + +## How it works + +1. Read Michel’s opening line, write your reply and select "Say it", or open "Or choose a reply". +2. Read Michel’s response. Reply once more to finish the conversation. +3. Read the review of both replies. Select "Start over" to try another path. + +Unclear or unrelated replies get guidance and open the five reply choices. Michel does not answer, and the round stays available. A pause shows a note that you return ten minutes later. Some endings include stage notes. + +## Evidence + +In a randomized study of 120 healthcare professionals, practising branching conversations with feedback improved three of four motivational interviewing measures more than further study of the material ([Reger et al., 2020](https://doi.org/10.1001/jamanetworkopen.2020.17348)). This supports conversation practice with consequences and feedback, but did not test this pattern. The content adapts the Gottman Institute's examples of repair attempts and the four horsemen from couple relationships to a workplace exchange ([Wilde, 2012](https://www.gottman.com/blog/manage-conflict-repair-and-de-escalate/); [Gottman Institute, 2013](https://www.gottman.com/blog/the-four-horsemen-the-antidotes/)). The decision model was tested only on agent-written answers. This two-round conversation and its effects on workplace conversations have not been tested with learners. + +## Accessibility + +Daily proxy caps show and announce the reason and midnight UTC reset once, keep focus in place and leave the fallback usable. + +Meets the shared baseline in the root README. + +- Each completed turn moves focus to Michel’s new line, including the final line. The reply controls hide or change only after focus arrives, so focus never falls to the page body. Focus announces the line once; the status region stays empty. Tab reaches the reply box, or "Start over" at the end. +- While a reply is sending, "Say it" keeps its label and focus, and pressing it again sends nothing. "Sending…" appears beside it. If the reply takes more than a second, the status region says "Sending…" once. +- If automatic checking fails while the reply box or "Say it" has focus, focus moves to the message "Automatic checking is unavailable. Choose a reply to continue." before the reply box hides. Focus reads the message, so the status region stays empty, and the next Tab reaches the first reply. If focus is elsewhere, the status region announces the message once. +- Unsure and off-script lines use the single status region without moving focus. Disclosures open immediately. +- Reply buttons have visible focus. Speech bubbles differ in position and speaker label, with borders in forced colours. +- Without the decision model, five reply buttons replace the reply box and support every path. Without JavaScript, nested disclosures show all 25 paths, stage notes and reviews. +- English and Québec French use identical fields. The French conversation uses vous. + +## Content fields + +All content is authored plain text, escaped before rendering. + +| Field | Meaning | +| --- | --- | +| `setup` | Two sentences setting the meeting scene. | +| `person` | Michel’s `name`, `role` and avatar `initial`. | +| `subject`, `opening` | Composer subject and Michel’s opening line. | +| `branches` | The five fixed branch identities. | +| `branches[branch].line`, `.endings` | Michel’s first reply, then his final reply for each second move. | +| `branches[branch].move`, `.debrief` | Name of the learner’s response and one sentence about its effect. | +| `examples[node][branch]` | Five example replies for `opening` and each branch node, at most 1,200 characters each. | +| `stageNotes` | Optional italic ending notes keyed `first:second`. | +| `pauseNote` | Return to the conversation after a pause. | +| `unsure`, `offScript` | Helpful guidance that does not advance the conversation. | + +The schema and validator reject unknown fields, missing branch IDs and blank text. Keep the English opening and first replies identical to the proxy’s `MICHEL_REPLIES`; tests check every line. French must convey the same context. To change the work-plan scenario, change and evaluate the proxy questions too. + +## Logic + +Authored text is capped at 120 characters for titles and labels, 400 for questions and prompts, 300 for options, and 1,500 for answers, explanations and passage text. Avatar `person.initial` is at most two characters; example replies stay capped at 1,200. The schema gives each field's limit; `validateContent` enforces it. + + +`logic.js` imports branch identities, the two-round limit, reply limit and decision model's confidence gate from the browser-safe `proxy/logic/03-contract.js`. That module owns the gate. + +| Export | Returns | +| --- | --- | +| `validateContent(content)` | Nothing; throws an `Error` naming the bad field. | +| `start()` | `{ node: 'opening', round: 0, history: [], end: false }`. Also resets the conversation. | +| `feedback(answers, model?)` | A branch, `'unsure'` or `'off_script'`, from `{ branch: { choice, confidence } }`. Without a decision model, uses Jev's confidence gate, also used by the default Perplexity provider. Invalid answers throw. | +| `turn(content, state, branch, reply)` | `{ state, line, note }`. History stores `{ branch, reply }`; `end` becomes true after two moves. Unsure/off-script keep the state and return guidance as `note`. Further turns after the end throw. | +| `validateState(value)` | Clean `{ conversation, draft }`, or `null` if the history, node, round, end flag or draft is invalid. | +| `BRANCHES`, `REPLY_LIMIT` | The proxy-owned branch identities and maximum reply length. | + +Pass `state: { read, write }` to save the draft and conversation. Invalid saved state is ignored. Editing during a check discards its result; restarting cancels requests. Repeated enhancement returns the same instance. `destroy()` removes listeners, cancels requests and restores the static script. + +## Use it + +Copy this folder, `lib/` and `proxy/logic/03-contract.js`, preserving relative paths. Include `lib/base.css`, then `pattern.css`. Give each instance a unique id prefix. + +```js +import { render } from './patterns/conversation/render.js'; +import { enhance } from './patterns/conversation/enhance.js'; +import { strings } from './patterns/conversation/strings.js'; +import { createAsk } from './lib/ask.js'; + +const markup = render(content, strings.en, { id: 'meeting', lang: 'en' }); +const ask = createAsk({ endpoint: '/api/patterns', fetch: window.fetch.bind(window) }); +enhance(document.querySelector('[data-lp-pattern="conversation"]'), { content, strings: strings.en, ask }); +``` + +The shared client calls `ask('03-branch', { node, reply }, { challengeSlot, signal })`. Its `config()` supplies the decision model and bilingual data notice below the reply box. Turnstile appears inline after a clearance refusal. Missing `ask`, failed configuration, invalid answers or a failed request opens the reply choices. The demo uses a fixed classification and sends no text. + +## Adapt it with your agent + +> Adapt the example replies and reviews for [audience]. Keep the work-plan scenario, five branch IDs, two rounds, Michel’s opening and first lines, and all 25 endings. Write one plain review sentence per response and keep example replies under 1,200 characters. Write English and Québec French together, using vous. Keep authored dialogue and no scores. + +## Licence + +[MIT](../../LICENSE). Keep the licence notice with copied code. diff --git a/docs/provenance/learning-patterns/patterns/course-lookup/README.md b/docs/provenance/learning-patterns/patterns/course-lookup/README.md new file mode 100644 index 0000000..d853a2c --- /dev/null +++ b/docs/provenance/learning-patterns/patterns/course-lookup/README.md @@ -0,0 +1,128 @@ +--- +title: Instant course lookup +title_fr: Trouvez une réponse dans le cours +summary: Ask a question to find an authored answer or course section. Save unanswered questions in your tab. +section: course +ai: yes +offline: yes +learners: not tried +--- +# Instant course lookup + +Ask about Assertive at work. The decision model picks an authored answer or a section of the course. It writes no answers. + +## Use it when + +Use it across a course with a small, stable catalogue. The demo has eight FAQ entries and six course sections. Both shapes use the same renderer and enhancer, configured with `kind: 'faq' | 'sections'`. + +## Avoid it when + +For catalogues with more than about 30 entries, first narrow the candidates with embeddings. Check the chosen candidates against the learner's question before showing them. + +## How it works + +1. Type at least 10 characters, excluding spaces at the start and end. A pause checks changed text. A final question mark or Enter checks it immediately. +2. Read the closest authored answer, or follow a link to the section. Two close matches can appear together. +3. If there is no match, choose "Add to the question bank". Your question appears as "Waiting for an answer". You can remove it. + +The wait is 2.5 times the median of the last nine inter-key gaps, between 400 and 900 ms. It defaults to 700 ms until three gaps are available. A silent "Looking…" line appears only after a request takes 300 ms. Results stay silent. A polite status region announces a changed match once, without moving focus. + +Editing cancels the older request and discards stale results. Each instance has at most one active request. All instances share 30 automatic checks per page session. Enter still works after the cap. Reloading resets it. Proxy limits can stop checks sooner. + +### Question bank and data + +The shared data notice explains the decision service. The offline demo uses fixed matches and sends no text. Live lookup sends the question to the decision model through the proxy. The proxy stores no question text. + +The question bank keeps added questions in this tab. Reloading keeps them; closing the tab clears them. Each language and instance has its own bank. Three authored examples show two instructor replies and one reply from another learner. Those examples remain available through "Show all". Only added learner questions are stored. The demo stores no bank entries on a server and sends no notifications. A failed save shows an error and keeps the previous bank. The bank holds up to 100 added questions. It shows the latest ten questions, including authored examples when there are fewer than ten newer questions. "Show all" reveals the complete bank; "Show latest 10" collapses it again. + +A real course should send questions to its forum or instructor queue. Ask for consent before sending. For example, "Send this question to the course forum? Other learners and the instructor can read it." Decide who can answer and remove questions, and state how long they remain. + +Without JavaScript, the full FAQ appears as native disclosures. The outline is a linked list. If lookup fails, the same list appears. The bank stays available when lookup fails. + +## Evidence + +FAQs can give people a direct route to common answers. They need real user questions and readable lists. The mock questions here illustrate the format; they did not come from learner research ([Nielsen Norman Group, 2014](https://www.nngroup.com/articles/faqs-deliver-value/)). + +Help-seeking research distinguishes seeking help to understand from seeking an answer to finish quickly. Perceived emphasis on mastery was associated with more help-seeking in two studies of college classes ([Karabenick, 2004](https://doi.org/10.1037/0022-0663.96.3.569)). This supports treating questions as part of learning. It does not test instant lookup or this bank. + +[Dhakal et al., CHI 2018](https://doi.org/10.1145/3173574.3174220) measured a mean inter-key interval of 239 ms, and over 480 ms for slow typists, in a transcription task. [Nielsen, 1993](https://www.nngroup.com/articles/response-times-3-important-limits/) identifies about one second as the limit for uninterrupted flow. These inform the adaptive wait. They do not establish the multiplier, sample window or cap. Network time adds to the wait. + +This pattern has not been tested with learners. We have not measured whether immediate answers improve learning, help-seeking or course completion. Calibration uses authored questions, not learner questions. It does not establish accuracy in a real course. + +In 44 authored live checks, all 32 single-answer or unanswered cases matched the expected result; only 1 of 12 paired questions returned both entries at these gates. + +## Accessibility + +Daily proxy caps show and announce the reason and midnight UTC reset once, keep focus in place and leave the fallback usable. Checks then stop until reload. + +Meets the shared baseline in the root README. + +- Results and the delayed progress line use `aria-live="off"`. One empty `role="status"` region announces a new result. Typing produces no announcements. +- Enter starts a check. Shift+Enter adds a line break. Composition input finishes before lookup. Links and bank buttons use native keyboard behaviour. +- Native FAQ disclosures and outline links work without JavaScript. Failed checks reveal the same content. +- No animation, colour-only feedback or focus movement on results. Adding a question focuses its text in the bank. Removing a question moves focus to the next removal button or the question field. If lookup fails while a lookup control has focus, focus moves to the message "Lookup is unavailable. Browse the list below." before the controls hide. Focus reads the message, so the status region stays empty, and the next Tab reaches the first fallback disclosure or outline link. If focus is elsewhere, the status region announces the message once. +- English and Québec French use the same controls. French uses vous. +- Browser checks cover axe, keyboard use, narrow reflow, text spacing, reduced motion and forced colours. Screen reader listening requires a separate human check. + +## Content fields + +| Field | Meaning | +| --- | --- | +| `kind` | `faq` or `sections`. | +| `prompt` | Visible heading and question field label, up to 400 characters. | +| `course` | Optional course name, up to 120 characters; required when the prompt contains `{course}`. Every occurrence renders in italics. | +| `entries` | Eight FAQ entries or six sections, in the proxy's fixed ID order. | +| `entries[].id`, `title` | Fixed identity and authored question or section title. | +| `entries[].answer` | Short authored FAQ answer, for `faq` only. | +| `entries[].summary` | One-line section summary, for `sections` only. | +| `seeds` | Three authored bank examples, with unique `id`, `question`, `author` and `answer`. | +| `seeds[].author` | `instructor` for two examples; `learner` for one. | + +All text is escaped. Unknown fields, wrong IDs and empty text fail validation. Keep the displayed catalogue consistent with the server-owned catalogue. To change topics, change both and calibrate the questions again. Section results link to the outline anchors that `render` creates. + +## Logic + +Authored text is capped at 120 characters for titles and labels, 400 for questions and prompts, 300 for options, and 1,500 for answers, explanations and passage text. The schema gives each field's limit; `validateContent` enforces it. + + +The proxy owns both catalogues and the Choice questions. The browser sends only the question to `20-faq` or `21-sections`. Both use Perplexity Decider v1.1. The best entry must reach 0.35 probability and the decision's confidence must reach 0.35. A second entry appears if its probability also reaches 0.35 and is within 0.20 of the best. A winning or tied `none` suppresses matches. These are selection probabilities, not evidence that an answer is correct. + +| Export | Returns or value | +| --- | --- | +| `validateContent(content)` | Nothing; throws an `Error` naming an invalid field. | +| `lookup(content, answers)` | Zero, one or two authored entries, best first. Invalid Choice distributions throw. | +| `validateState(value)` | A clean `{ questions: string[] }`, or `null`. | +| `QUESTION_LIMIT`, `AUTO_CHECK_LIMIT`, `BANK_LIMIT` | 500 characters, 30 automatic checks, 100 added questions. | + +`proxy/logic/20-faq.js` and `21-sections.js` own the catalogue IDs and match gates, shared by browser and proxy. `lib/typing-pause.js` exports `typingPause({ minChars = 10, questionMark = true } = {})`, with `key(time)`, `wait()` and `delay(text, checked, enter = false)`. Delay returns milliseconds or `null` when no check is due. [Feedback while you type](../live-feedback/README.md) shares this module, with a 20-character minimum and no question-mark shortcut. + +`enhance(root, { content, strings, ask, state })` returns `{ destroy() }`. Repeated calls return the same instance. Optional `state: { read, write }` replaces session storage. Use a separate state adapter for each instance. Invalid saved values are ignored. `destroy()` cancels work, removes listeners and restores server markup. The page allowance survives re-enhancement. + +## Use it + +Each block asks one Choice question with a 500-character cap. Each reserves at most 8,192 input tokens, including catalogue, escaped UTF-8 state and 1,024 framing tokens. Request and cost-guard tests cover these bounds and failed calls. + +Copy this folder, `lib/` and both proxy decision modules, preserving relative imports. Include `lib/base.css` before `pattern.css`. For live checks, set up the [proxy](../../proxy/README.md). Keep server catalogues and gates with it. Check the provider's data terms before using its notice. + +```js +import { render } from './patterns/course-lookup/render.js'; +import { enhance } from './patterns/course-lookup/enhance.js'; +import { strings } from './patterns/course-lookup/strings.js'; +import { createAsk } from './lib/ask.js'; + +const markup = render(content, strings.en, { id: 'course-faq', lang: 'en' }); +// Insert markup into your page, then enhance its root. +enhance(document.querySelector('[data-lp-pattern="course-lookup"]'), { + content, strings: strings.en, ask: createAsk() +}); +``` + +The default `browserState` adapter uses sessionStorage with the key `lp:course-lookup::`. Give each instance a stable, unique prefix so its bank survives reloads. Use a separate prefix for each course. + +## Adapt it with your agent + +> Replace the FAQ and outline for [course]. Keep one shared renderer and enhancer, server-owned Choice catalogues, authored answers and a browser-only bank. Write English and Québec French together, using vous. Calibrate the questions and gates before using live lookup with learners. + +## Licence + +MIT. Keep the [licence notice](../../LICENSE) with copied code. diff --git a/docs/provenance/learning-patterns/patterns/dont-know/README.md b/docs/provenance/learning-patterns/patterns/dont-know/README.md new file mode 100644 index 0000000..dfbf2c8 --- /dev/null +++ b/docs/provenance/learning-patterns/patterns/dont-know/README.md @@ -0,0 +1,172 @@ +--- +title: '"I don''t know" as an answer' +title_fr: « Je ne sais pas » comme réponse +summary: Answer a quiz with an "I don't know" option, then see your score, explanations and questions to review. +section: question +ai: no +offline: yes +learners: not tried +--- +# "I don't know" as an answer + +Answer a quiz with an "I don't know" option. See your score, explanations and questions to review. + +## Use it when + +Use it for short practice quizzes where learners need a way to say they are unsure. Set points for right, wrong and unknown answers. Losing points for wrong answers may discourage guessing. + +## Avoid it when + +Test the scoring with your learners before using it for assessment. + +## How it works + +1. Read the scoring rule and choose an answer for each question. Choose "I don't know" when you are unsure. +2. Select "Check my answers". Fill in any answers you left blank. +3. See a mark beside each chosen answer. Wrong and unknown answers also show the correct answer and an explanation. +4. Read your score. Follow the review links to questions you got wrong or answered with "I don't know". +5. Select "Start over" to clear your answers and try again. + +## Write this in .prax + +In Praxity's `.prax` format, an "I don't know" option is a setting on an ordinary single-answer `choice` question. Studio adds the option itself, so authors do not write it among the answers. Option narration reads it last, and reporting treats it as a neutral "don't know" answer. + +Studio does not use this library's configurable right/wrong/unknown points. An "I don't know" answer counts as answered and earns no points. + +English, unscored practice (Praxity Studio from commit `c57f57b`, 2026-10-08): + +```prax +--- +lang: en +--- +# Shapes + +::: choice What describes a triangle? {dontKnow=true scored=false required=false} +correct: A triangle has three straight sides. +incorrect: Count the straight sides of a triangle. +(x) A shape with three straight sides +( ) A shape with four straight sides +::: +``` + +French, unscored practice (Praxity Studio from commit `c57f57b`, 2026-10-08): + +```prax +--- +lang: fr +--- +# Les instruments de mesure + +::: choice Quel instrument mesure la température ? {dontKnow=true scored=false required=false} +correct: Un thermomètre mesure la température. +incorrect: Un chronomètre mesure une durée. +(x) Un thermomètre +( ) Un chronomètre +::: +``` + +Private practice (Praxity Studio from commit `ad4952f`, 2026-10-18): + +```prax +--- +lang: en +--- +# Units of time + +::: choice How many minutes are in one hour? {dontKnow=true private=true scored=false required=false} +correct: One hour contains 60 minutes. +incorrect: An hour is divided into 60 equal minutes. +(x) 60 minutes +( ) 100 minutes +::: +``` + +Private practice answers stay in the learner's browser and are not sent to the LMS; private questions must be optional, unscored and outside question groups. + +## Evidence + +When learners can withhold an answer, the answers they give are more accurate. The gain depends on how well they judge their knowledge ([Koriat and Goldsmith, 1996](https://doi.org/10.1037/0033-295X.103.3.490)). Medical exams have also used certainty-based marking. It rewards learners for being honest about how sure they are ([Gardner-Medwin, 1995](https://doi.org/10.1080/0968776950030113)). + +An "I don't know" option reduced guessing in a vocabulary test but also hid partial knowledge ([Zhang, 2013](https://eric.ed.gov/?id=EJ1027592)). In several studies, penalties led women to skip more questions than men. Removing penalties narrowed score gaps ([Baldiga, 2014](https://doi.org/10.1287/mnsc.2013.1776); [Coffman and Klinowski, 2020](https://doi.org/10.1073/pnas.1920945117)). + +The pattern follows Freire's and hooks's critique of teaching that treats teachers as knowing everything and learners as knowing nothing. See Freire, *Pedagogy of the Oppressed*, 1970, and hooks, *Teaching to Transgress*, 1994. Recognising the limits of one's knowledge predicted more effort to learn in five studies ([Porter et al., 2020](https://doi.org/10.1016/j.lindif.2020.101888)). + +This scoring rule and its effect on test anxiety have not been tested. Keep wrong-answer penalties low-stakes. + +## Accessibility + +Follows the shared baseline in the root README. + +- Each unanswered question has one error message linked to its group. Its options expose an invalid state. Focus moves to the first unanswered option. +- Submission focuses the score to announce it once. The status region stays silent. Review links focus the question they name. +- Start over focuses the first option. Saved results restore without moving focus or making an announcement. + +## Content fields + +Content is plain text. Rendering escapes HTML characters. + +| Field | Meaning | +| --- | --- | +| `title` | Nonempty quiz title in the header. | +| `questions` | At least one question, in display order. | +| `questions[].id` | Unique question id using letters, digits, underscores or hyphens. | +| `questions[].text` | Question text shown as the fieldset legend. | +| `questions[].options` | At least one option. Ids must be unique within the question. | +| `questions[].options[].id` | Id using letters, digits, underscores or hyphens. `dont-know` is reserved. | +| `questions[].options[].text` | Option label. | +| `questions[].correct` | Id of an option in this question. | +| `questions[].explanation` | Explanation for wrong or unknown answers, also shown without JavaScript. | +| `points.right`, `points.wrong`, `points.unknown` | Authored finite numbers, including fractions or negative values. | + +`validateContent` rejects unknown fields, empty strings, duplicate ids and invalid correct-option references. Whitespace-only strings are allowed. It supplies the cross-field checks that ordinary JSON Schema tools need, recorded as `x-uniqueBy` and `x-optionReference`. + +## Logic + +Authored text is capped at 120 characters for titles and labels, 400 for questions and prompts, 300 for options, and 1,500 for answers, explanations and passage text. The schema gives each field's limit; `validateContent` enforces it. + + +`logic.js` has no DOM code. Hosts can build their own interface. + +| Export | Returns | +| --- | --- | +| `DONT_KNOW` | The reserved option identity `dont-know`. | +| `validateContent(content)` | Nothing. Throws an `Error` naming the first bad field. | +| `score(content, picks)` | `{ points, total, right, wrong, unknown, unanswered }`. Each group contains question identities in content order. `points` and `total` keep full precision; `total` is `questions.length * points.right`. Missing picks are unanswered. Invalid picks throw. | +| `validateState(content, value)` | A copied `{ picks, shown }`, or `null` for invalid saved state. A shown result requires complete picks. | +| `displayPoints(value, positive = false, lang = 'en')` | Localized text rounded to hundredths, with a mathematical minus and an optional plus sign. | +| `format(template, values)` | Text with known `{key}` placeholders replaced once. Inserted values stay literal. | + +Without JavaScript, choose answers with the radios, then open "Answers" for correct options and explanations. + +`points` must satisfy `right > unknown >= wrong`. Only the displayed text is rounded to hundredths. + +The example scores +1 for right, −1 for wrong and 0 for "I don't know". Submission locks answers until reset. The decorative score ring stays between empty and full. It is empty for negative scores or nonpositive totals. Text always shows the actual score. Zero counts and empty review lists are hidden. + +Pass `state: { read, write }` to save `{ picks: { [questionId]: optionId | 'dont-know' }, shown }`. Invalid state is ignored. Partial picks restore when `shown` is false. Shown results require complete picks and restore silently. Each pick, submission and reset saves state. Repeated enhancement returns the same instance. `destroy()` removes listeners, errors and results and restores native answers. + +## Use it + +Copy `patterns/dont-know/` and `lib/`, keeping their relative paths. Link `lib/base.css`, then `patterns/dont-know/pattern.css`. Use a unique id prefix per instance. Render on the server; call `enhance` in the browser. + +```js +import { render } from './patterns/dont-know/render.js'; +import { enhance } from './patterns/dont-know/enhance.js'; +import { strings } from './patterns/dont-know/strings.js'; +const content = { + title: 'Money basics', + questions: [{ id: 'fund', text: 'What is an emergency fund for?', + options: [{ id: 'unexpected', text: 'Unexpected expenses' }], + correct: 'unexpected', explanation: 'It covers unexpected expenses.' }], + points: { right: 1, wrong: -1, unknown: 0 } +}; +document.querySelector('main').innerHTML = render(content, strings.en, { id: 'practice', lang: 'en' }); +const instance = enhance(document.querySelector('[data-lp-pattern]'), { content, strings: strings.en }); +``` + +## Adapt it with your agent + +> Rewrite the dont-know examples for [topic] and [audience]. Keep `{ title, questions: [{ id, text, options: [{ id, text }], correct, explanation }], points: { right, wrong, unknown } }`. Use descriptive unique option ids and valid correct-option references. Reserve `dont-know` for the uncertainty option. Explain wrong and unknown answers. Keep English and Québec French together; use vous in French. A model may only choose authored messages. Follow the pattern contract, including state, accessibility and CSS tokens. Update tests and show both languages for review. Keep the limits on untested scoring and penalties. + +## Licence + +MIT. Keep the [licence notice](../../LICENSE) with copied code. diff --git a/docs/provenance/learning-patterns/patterns/explain-back/README.md b/docs/provenance/learning-patterns/patterns/explain-back/README.md new file mode 100644 index 0000000..4f0bb72 --- /dev/null +++ b/docs/provenance/learning-patterns/patterns/explain-back/README.md @@ -0,0 +1,104 @@ +--- +title: Explain what you learned +title_fr: Expliquer ce que vous avez appris +summary: Explain a short lesson, check three key ideas and reread any you missed. +section: reading +ai: yes +offline: yes +learners: not tried +--- +# Explain what you learned + +Explain a lesson in your own words and check which key ideas you included. + +## Use it when + +Use it after a short reading. This example checks three ideas: stonewalling, announcing a pause and returning to the conversation. The feedback helps you revise your explanation. + +## Avoid it when + +It gives no grade. + +## How it works + +1. Read the three lesson sections, then select "I'm ready to explain it". +2. Explain stonewalling and taking a time-out to a new manager in two sentences. Select "Check my explanation". Verification appears if needed. +3. Read the idea count and feedback for each idea: "Found", "To add" or "Not sure". +4. Select "Read the text again" or a feedback link to revisit the lesson. Your draft and result stay available. Return to your explanation, revise it and check again. +5. When all three ideas are found, the model answer opens. If automatic checking is unavailable, use the checklist and compare with the model answer. + +## Evidence + +Prompts to explain connections in learning material produced an average benefit of 0.55 standard deviations across 69 effect sizes ([Bisra et al., 2018](https://doi.org/10.1007/s10648-018-9434-x)). Explaining an idea to a new manager also draws on research about learning by teaching ([Fiorella and Mayer, 2016](https://doi.org/10.1007/s10648-015-9348-9)). These studies support asking learners to explain in their own words, but did not test this pattern. Research on explanatory feedback informs the hints and links to missing ideas ([Van der Kleij et al., 2015](https://doi.org/10.3102/0034654314564881)). With JavaScript, reading and explaining are separate steps. Learners can return to the reading at any time. The decision model was tested only on agent-written answers. The two-sentence task, missing-idea checks and passage links have not been tested with learners. + +## Accessibility + +Daily proxy caps show and announce the reason and midnight UTC reset once, keep focus in place and leave the fallback usable. + +Meets the shared baseline in the root README. + +- Results appear on submit and announce one summary without moving focus. +- While a check runs, "Check my explanation" keeps its label and focus, and pressing it again sends nothing. "Checking your explanation…" appears beside it. If the check takes more than a second, the status region says it once. +- If automatic checking fails while "Check my explanation" has focus, focus moves to the first idea in the checklist before the button hides. The checklist's legend is the message, so the screen reader reads it once as focus enters the group, and the status region stays empty. If focus is elsewhere, the status region announces the message once. +- Switching steps reveals the new step, waits for browser layout, focuses its heading, then hides the previous step. Feedback links open the reading step and focus the matching lesson heading. "Read the text again" stays available after a check. +- Each row has an icon and a word as well as colour. "To add" is orange; "Not sure" is neutral. +- Without JavaScript, the lesson, answer box, native checklist and model answer appear together. JavaScript adds the step controls. If automatic checking is unavailable, the explanation step offers the checklist and model answer. +- English and Québec French have the same fields and controls. + +## Content fields + +All fields are plain text and escaped when rendered. + +| Field | Meaning | +| --- | --- | +| `task` | The card title and writing task. | +| `model` | The authored model answer. | +| `ideas` | Exactly three ideas, ordered `stonewalling`, `pause`, `return`. | +| `ideas[].id` | The fixed question identity. | +| `ideas[].heading`, `ideas[].body` | The lesson heading and paragraph. | +| `ideas[].label` | The self-check statement. | +| `ideas[].met`, `ideas[].missed`, `ideas[].unsure` | Authored feedback for each decision. | + +Text must be nonempty. The schema and validator reject unknown fields and require the fixed idea IDs and order. Keep the meaning of the proxy questions when rewording. To change the topic, change and evaluate those questions too. + +## Logic + +Authored text is capped at 120 characters for titles and labels, 400 for questions and prompts, 300 for options, and 1,500 for answers, explanations and passage text. The schema gives each field's limit; `validateContent` enforces it. + + +`logic.js` is pure and imports the gate, idea identities and answer limit from `proxy/logic/`. Keep those browser-safe modules when copying the pattern. + +| Function | Returns | +| --- | --- | +| `validateContent(content)` | Nothing; throws an `Error` naming the bad field. | +| `feedback(content, answers)` | `{ count, total, allFound, items }`. Items are `{ id, mark, text, heading }`, with `mark: 'met'`, `'missed'` or `'unsure'`. Invalid decision model answers throw. | +| `validateState(value)` | A clean `{ answer, ticked }` draft, or `null` for invalid saved values. | +| `ANSWER_LIMIT` | The proxy-owned maximum answer length. | + +Pass `state: { read, write }` to save drafts and checklist ticks. Restoring either opens the explanation step. Check results stay available between steps but are not saved. Editing during a check discards its result. Repeated enhancement returns the same instance. `destroy()` removes listeners, cancels checks and restores the server markup with both steps and the native fallback visible. + +## Use it + +Copy this folder, `lib/` and `proxy/logic/`, preserving relative paths. Include `lib/base.css`, then `pattern.css`. Each instance needs a unique id prefix. Serve the proxy on the page's own origin. + +```js +import { render } from './patterns/explain-back/render.js'; +import { enhance } from './patterns/explain-back/enhance.js'; +import { strings } from './patterns/explain-back/strings.js'; +import { createAsk } from './lib/ask.js'; +// Server, with the English example loaded as content: +const markup = render(content, strings.en, { id: 'explain', lang: 'en' }); +// Browser, after the server markup reaches the page: +const ask = createAsk({ endpoint: '/api/patterns', fetch: window.fetch.bind(window) }); +enhance(document.querySelector('[data-lp-pattern="explain-back"]'), { content, strings: strings.en, ask }); +``` + +The shared `ask(block, fields, { challengeSlot, signal? })` client returns typed answers. Its cached `config()` supplies the English and French data notice. Turnstile loads after a clearance refusal and retries once with the solved token. `AskError.type` is `offline`, `refused`, `busy`, `budget` or `invalid`. Any failure, missing `ask` or failed configuration opens the self-check. The demo uses fixed answers and sends no text. + +## Adapt it with your agent + +> Adapt this example for [audience]. Keep unexplained withdrawal, announcing a pause, and agreeing when to return and following through. Keep the fields and idea order. Write useful feedback for each result. Write English and Québec French together, using vous. Preserve authored feedback, the self-check, lesson focus links, proxy gates, data notice and on-demand verification. Evaluate any changed proxy questions against authored sample labels before use. + +## Licence + +MIT. Keep the [licence notice](../../LICENSE) with copied code. diff --git a/docs/provenance/learning-patterns/patterns/first-answer/README.md b/docs/provenance/learning-patterns/patterns/first-answer/README.md new file mode 100644 index 0000000..a26bb22 --- /dev/null +++ b/docs/provenance/learning-patterns/patterns/first-answer/README.md @@ -0,0 +1,122 @@ +--- +title: Your first answer replayed +title_fr: Revenez sur votre première réponse +summary: Save your first answer, answer again at the end of the course, then compare what changed. +section: course +ai: no +offline: yes +learners: not tried +--- +# Your first answer replayed + +Save an answer at the start of a course. Answer again at the end, then compare what changed. + +## Use it when + +Use it when learners can answer the same question at the start and end of a course. Keep the question and self-check criteria the same. + +## Avoid it when + +Learners judge their own changes, so use another method to grade answers. + +## How it works + +1. Write an answer and select "Save my first answer". It stays as you wrote it, with the date you saved it. +2. At the end of the course, answer the same question and select "Compare". +3. Read your first answer below your new one. Tick the improvements you notice. The counter shows your ticks. +4. Select "Try again" to rewrite your new answer. Your first answer and ticks stay saved. +5. Select "Start over" to clear both answers and your ticks. + +## Evidence + +The improvement checklist asks learners to assess their work against named criteria. A meta-analysis of 175 studies linked self-assessment to better academic performance ([Yan et al., 2022](https://doi.org/10.1016/j.edurev.2022.100484)). Self-assessment works best for improving work rather than grading it ([Andrade and Valtcheva, 2009](https://doi.org/10.1080/00405840802577544)). + +Keeping the first answer as written avoids relying on memory. After a study skills course, students remembered their earlier skills as worse than they had originally rated them. This made the course seem more helpful than it was ([Conway and Ross, 1984](https://doi.org/10.1037/0022-3514.47.4.738)). Feedback on progress raised children's self-efficacy and writing achievement in two experiments ([Schunk and Swartz, 1993](https://doi.org/10.1006/ceps.1993.1024)). Showing the old and new answers lets learners see changes directly. + +Learners tend to rate their own work generously ([Dunlosky and Rawson, 2012](https://doi.org/10.1016/j.learninstruc.2011.08.003)). Ticks record changes they notice, not measured gains. Bringing back a first answer at the end of a course has not been tested directly. + +## Accessibility + +Follows the shared baseline in the root README. + +- Saved entries show the date and time in the placement's language. First and current answers have separate labels. +- Blank or long answers get errors linked to the text box. Saving focuses Skip in the demo, or the saved entry in a first-only placement. +- Compare focuses the revealed Day one heading before the saved answer and improvement checks. +- Try again silently focuses the current-answer field. Reset focuses the first-answer field, or the current-answer field in an end-only placement. +- Storage problems show visible messages. Valid entries restore silently. + +## Content fields + +Authors write plain text. Rendering escapes HTML characters. + +| Field | Meaning | +| --- | --- | +| `prompt` | Title and question. Keep the same at both course placements. | +| `checks` | At least one self-assessment check, in display order. | +| `checks[].id` | Unique id using letters, digits, underscores or hyphens. Keep stable across placements. | +| `checks[].label` | Visible checkbox label. | + +Strings must be nonempty. `validateContent` rejects unknown fields and duplicate ids. The schema's `x-uniqueBy` annotation needs the uniqueness check in `validateContent`. + +## Logic + +Authored text is capped at 120 characters for titles and labels, 400 for questions and prompts, 300 for options, and 1,500 for answers, explanations and passage text. The schema gives each field's limit; `validateContent` enforces it. + + +`logic.js` has no DOM code. Hosts can use it with their own interface. + +| Export | Returns | +| --- | --- | +| `MAX_LENGTH` | Answer limit of 2,000 characters. | +| `validateContent(content)` | Nothing. Throws an `Error` naming the first bad field. | +| `validateAnswer(text)` | `{ ok: true, text }` with trimmed text, or `{ ok: false, error: 'empty' \| 'tooLong' }`. Uses `MAX_LENGTH`. | +| `validateState(content, value)` | Copied `{ first, now, checks }`, or `null` for invalid state. | +| `emptyState(content)` | `{ first: null, now: null, checks }` with every check false. | +| `withFirstAnswer(content, state, text, savedAt)` | Copied state with first saved. Throws if first or now is already saved. | +| `withAnswerNow(content, state, text, savedAt)` | Copied state replacing only now. Keeps first and checks. | +| `withChecks(content, state, checks)` | Copied state with known checks set to booleans. Throws when now is absent. | + +Entries are `{ text, savedAt }`. Actions treat `null` state as empty and throw on invalid inputs. + +`render` accepts `stage: 'first' | 'end' | 'both'`, default `'both'`. First and end placements can sit on different course pages. Each has a notebook icon, the prompt as title, and dated saved answers. Headings are "Day one" and "End of the course". + +The demo shows both stages with a dashed timeline, a course note and Skip after saving. Skip opens and focuses the end heading. Restored answers open the end section directly. An end placement without a first answer explains the missing comparison and still accepts an answer. + +Without JavaScript, every placement shows the prompt, a first-answer textarea and a note about saving. + +Saving trims answers and limits them to 2,000 characters. The first entry stays fixed until reset. Compare makes the current-answer field read-only and hides its button. Try again keeps the draft and hides the comparison. Only the next Compare replaces the saved current answer. + +Optional host state has synchronous `read()` and `write(value)` methods. The host chooses storage; the pattern never uses localStorage. Without an adapter, answers last for the mounted instance. Across course pages, use the same prompt, check ids and adapter. Set the closing block to `stage: 'end'`. + +State is `{ first: { text, savedAt } | null, now: { text, savedAt } | null, checks: { [id]: boolean } }`. Dates are full ISO timestamps, written in UTC and displayed by `Intl.DateTimeFormat` using the block's `lang`. Reset writes `emptyState(content)`. + +State needs a boolean for each check. Saved answers must be trimmed, nonempty and within the limit, with valid ISO timestamps. Malformed or unreadable state shows a warning and blocks writes until reset. Failed writes keep the previous record and show an error. Valid state restores silently. A saved now answer restores the locked comparison, even if the learner left while rewriting. + +Repeated `enhance` calls preserve server elements and return the same instance. `destroy()` removes listeners and restores the no-JavaScript view. Placements sharing an adapter reread it before each action, so a stale placement cannot overwrite a newer answer. The host handles concurrent writes across tabs or devices. + +## Use it + +Copy `patterns/first-answer/` and `lib/`, keeping their relative paths. Link `lib/base.css`, then `patterns/first-answer/pattern.css`. Use a unique id prefix per instance. + +```js +import { render } from './patterns/first-answer/render.js'; +import { enhance } from './patterns/first-answer/enhance.js'; +import { strings } from './patterns/first-answer/strings.js'; +const content = { + prompt: 'What would you say to a colleague who keeps interrupting you?', + checks: [{ id: 'view', label: 'Asks how they see the situation' }] +}; +const markup = render(content, strings.en, { id: 'opening', lang: 'en', stage: 'first' }); +// Send markup to the browser, then: +const instance = enhance(document.querySelector('[data-lp-pattern="first-answer"]'), { + content, strings: strings.en, state: hostState +}); +``` + +## Adapt it with your agent + +> Adapt the first-answer examples to [topic] for [audience]. Use the same prompt at both course placements. Keep `{ prompt, checks: [{ id, label }] }` and stable, unique ids. Write checks for changes learners can see in their answers. Write English and Québec French together, using vous. Preserve the first answer, state, stages, accessibility and CSS tokens. A model may only choose authored messages. Update examples and tests. Show both languages for review. + +## Licence + +MIT. Keep the [licence notice](../../LICENSE) with copied code. diff --git a/docs/provenance/learning-patterns/patterns/highlight/README.md b/docs/provenance/learning-patterns/patterns/highlight/README.md new file mode 100644 index 0000000..eed3510 --- /dev/null +++ b/docs/provenance/learning-patterns/patterns/highlight/README.md @@ -0,0 +1,117 @@ +--- +title: Highlight the key passage +title_fr: Mettre en évidence le passage clé +summary: Mark key ideas or text that answers a question, then compare with the author's choices. +section: reading +ai: no +offline: yes +learners: not tried +--- +# Highlight the key passage + +Mark key ideas or text that answers a question. Then compare your marks with the author's choices in the passage. + +## Use it when + +Use it to find key ideas or evidence for a question. Keep passages short and split them into meaningful pieces. + +## Avoid it when + +Learners select whole pieces, so use another pattern for selecting individual words. Searching visible text does not test recall. + +## How it works + +1. Read the passage and the question, if there is one. +2. Mark key ideas or text that answers the question. The counter shows your marks and the limit. Select marked text again to unmark it. +3. Select "Check". Read feedback beside your marks and any text you missed. +4. Read how many you found. Select "Start over" to try again. + +## Evidence + +Highlighting alone is a weak study strategy. A major review rated it low utility ([Dunlosky et al., 2013](https://doi.org/10.1177/1529100612453266)). A meta-analysis of 36 articles found that marking text yourself improved memory but not comprehension. Text highlighted by an instructor improved both ([Ponce, Mayer and Méndez, 2022](https://doi.org/10.1007/s10648-021-09654-1)). Students often mark too much or choose the wrong text. Short training helps them choose what to mark ([Miyatsu, Nguyen and McDaniel, 2018](https://doi.org/10.1177/1745691617710510); [Leutner, Leopold and den Elzen-Rump, 2007](https://doi.org/10.1027/0044-3409.215.3.174)). + +This pattern asks learners to find key ideas or answer a question, then compare with the author's choices. Comparing answers with a correct standard helped students judge their learning more accurately in several experiments ([Lipko et al., 2009](https://doi.org/10.1037/a0017599); [Rawson, O'Neil and Dunlosky, 2011](https://doi.org/10.1037/a0024749)). Finding evidence in visible text practises reading, not recall. This combination has not been tested. + +## Accessibility + +Follows the shared baseline in the root README. + +- Each chunk is an inline span with a button role and its own tab stop. Tab and Shift+Tab follow passage order. Space or Enter toggles a mark, including in screen-reader browse mode. +- Arrow shortcuts work when the screen reader passes them to the page. Left or Up moves back; Right or Down moves forward. They wrap at the ends. Home and End jump to the first and last chunks. Each chunk exposes its pressed state and links to keyboard instructions. +- Checked chunks stay readable and navigable, with feedback beside correct marks, missed targets and other selections. +- At the limit, selecting another chunk shows and announces a message below the passage. Removing a mark clears it. +- Found targets have green highlights and solid underlines; missed targets have dashed underlines and no highlight. Other selections stay yellow, with readable notes in neutral pills. Icons and words identify outcomes, including in forced colours. +- Check announces the target count. If Check has focus when it hides, focus moves to Start over. Reset focuses the first chunk silently. +- Known screen reader behaviour: when NVDA reads line by line and a chunk wraps onto the next line, it says "toggle button, not pressed" again at the start of that line. NVDA does the same for any link that wraps. The chunks stay inline so the passage reads as paragraphs. + +## Content fields + +Authors write plain text. Rendering escapes HTML characters. + +| Field | Meaning | +| --- | --- | +| `mode` | `key` or `evidence`. Both use `key: true` for targets. | +| `title` | Article title. | +| `question` | Required in evidence mode; optional in key mode. Appears above the passage. | +| `maxMarks` | Optional positive integer. Defaults to the target count in evidence mode, or that count plus one in key mode. | +| `paragraphs` | Nonempty array of paragraphs, each a nonempty array of chunks. | +| `paragraphs[][].id` | Unique across the passage. Letters, digits, underscores and hyphens only. | +| `paragraphs[][].text` | Text without leading or trailing whitespace. One space joins adjacent chunks. | +| `paragraphs[][].key` | Optional boolean. `true` marks a target. At least one target is required. | +| `paragraphs[][].note` | Optional explanation for a selected non-target. Follows "Not a key idea" in key mode; replaces default wrong-selection feedback in evidence mode. Also appears in the native answer. | + +Text fields must contain non-whitespace text. Unknown fields and duplicate ids are rejected. The schema describes the shape, question requirement and required target. Its `x-uniqueChunkIds` annotation needs the global id check in `validateContent`. + +`examples/en.json` and `fr.json` use evidence mode, as does the demo. `en-key.json` and `fr-key.json` use key mode with the same passage. + +## Logic + +Authored text is capped at 120 characters for titles and labels, 400 for questions and prompts, 300 for options, and 1,500 for answers, explanations and passage text. The schema gives each field's limit; `validateContent` enforces it. + + +`logic.js`, `strings.js` and `content.schema.json` have no DOM dependencies. Praxity Studio can use them with its own interface. + +| Function | Returns | +| --- | --- | +| `validateContent(content)` | Nothing. Throws an `Error` naming the first bad field. | +| `markLimit(content)` | Maximum marked chunks, using `maxMarks` or the mode default. Requires validated content. | +| `check(content, markedIds)` | `{ found, total, marked, wrong, items }`. Counts selected targets, all targets, selected chunks and selected non-targets. Duplicate marks count once. Items follow passage order and contain `{ id, text, marked, outcome, note }`. `outcome` is `correct`, `missed`, `wrong` or `unmarked`; `note` is authored text or `null`. Unknown ids, invalid arrays and selections over the limit throw. | +| `validateState(content, value)` | Copied `{ marked: string[], shown: boolean }` or `null`. Rejects unknown ids, duplicate or excess marks, extra fields and invalid shapes. Requires validated content. | + +Without JavaScript, the passage is plain text. Native "Answer" details lists targets and all authored notes. + +Check locks marks until reset. Mark counts update silently. Use a unique id prefix per instance. + +Optional `state: { read(), write(value) }` stores `{ marked, shown }`. Valid results restore silently; invalid values are ignored. Adapter errors propagate to the host. + +Enhancement adds button roles, tab stops and pressed states to the existing spans. It preserves paragraphs and feedback nodes. Repeated enhancement returns the same instance. `instance.destroy()` removes listeners and interaction attributes and restores the plain passage and native answer. The root can then be enhanced again. + +## Use it + +Copy `patterns/highlight/` and `lib/`, keeping their relative paths. Link `lib/base.css`, then `patterns/highlight/pattern.css`. Render on the server, then enhance in the browser. + +```js +import { render } from './patterns/highlight/render.js'; +import { enhance } from './patterns/highlight/enhance.js'; +import { strings } from './patterns/highlight/strings.js'; +const content = { + mode: 'evidence', title: 'Taking a break', question: 'Why say you will return?', + paragraphs: [[ + { id: 'pause', text: 'You need a moment to think.' }, + { id: 'return', text: 'They know you will return.', key: true } + ]] +}; +const markup = render(content, strings.en, { id: 'practice', lang: 'en' }); +// Send markup to the browser, then: +const instance = enhance(document.querySelector('[data-lp-pattern="highlight"]'), { + content, strings: strings.en +}); +``` + +## Adapt it with your agent + +> Adapt the highlight examples to [topic] for [audience]. Keep `{ mode, title, question?, maxMarks?, paragraphs: [[{ id, text, key?, note? }]] }`. Choose key ideas or evidence for one focused question. Split the passage into meaningful pieces. Keep ids unique and at least one target. Explain selected non-targets in notes. Write English and Québec French together, using vous. Preserve the plain passage, native answer, keyboard controls, local feedback, state, escaping and CSS tokens. Do not claim that searching visible text tests recall. Update examples and tests. Show both languages for review. + +## Licence + +MIT. Keep the [licence notice](../../LICENSE) with copied code. diff --git a/docs/provenance/learning-patterns/patterns/journal/README.md b/docs/provenance/learning-patterns/patterns/journal/README.md new file mode 100644 index 0000000..8dd5462 --- /dev/null +++ b/docs/provenance/learning-patterns/patterns/journal/README.md @@ -0,0 +1,113 @@ +--- +title: Journal entry with a nudge +title_fr: Entrée de journal avec un petit rappel +summary: Reflect on your week, save your entry and ask for a question or support message. +section: question +ai: yes +offline: yes +learners: not tried +--- +# Journal entry with a nudge + +Reflect on practising assertiveness this week. Save your entry in your tab or ask for one optional question. + +## Use it when + +Use it for weekly course reflections. Each suggestion asks about the first missing detail: a situation, your response, a next step or when to try it. + +## Avoid it when + +There is no score or checklist. + +The distress check was tested only on agent-written answers. It shows an authored support message instead of a question. It does not diagnose, contact anyone or send alerts. Replace the message with your real support details. Test the decision model with your own examples before use with learners. + +## How it works + +1. Write about your week under today’s date. +2. Select "Save" to keep the entry in this tab. Saving never asks for a suggestion. +3. Select "Get a suggestion" for one optional question, a completion message or a support message. + +Editing during a request discards the suggestion. Select "Get a suggestion" again for the revised entry. + +Without the decision model, all four questions open with support text below. Without JavaScript, you can write and read the questions and support text, but cannot save. + +## Evidence + +A review of 29 studies in health professions identified guidance and a supportive setting as conditions for reflection ([Mann et al., 2009](https://doi.org/10.1007/s10459-007-9090-2)). Asking what to try and when draws on research about linking a future situation to an action ([Gollwitzer and Sheeran, 2025](https://doi.org/10.1146/annurev-psych-021524-110536)). Optional questions follow guidance on teaching that supports learner choice ([Reeve and Halusic, 2009](https://doi.org/10.1177/1477878509104319)). Detecting distress in text is unreliable: a review of 75 studies of mental-health prediction from social media found problems with how labels were defined and validated ([Chancellor and De Choudhury, 2020](https://doi.org/10.1038/s41746-020-0233-7)). The support message is a precaution. Offer help whether or not the decision model flags distress. The decision model was tested only on agent-written answers. Workplace reflection benefits, the one-question limit and distress detection have not been tested with learners. + +## Accessibility + +Daily proxy caps show and announce the reason and midnight UTC reset once, keep focus in place and leave the fallback usable. + +Meets the shared baseline in the root README. + +- The prompt labels the large writing area. Today’s date uses the page language. +- Each submitted result or save message uses one status region. Suggestions keep focus on the action; typing does not announce or request feedback. +- While a suggestion is pending, "Get a suggestion" keeps its label, and pressing it again sends nothing. "Reading…" appears beside it. If the suggestion takes more than a second, the status region says "Reading…" once. +- Native questions open immediately. Support text and questions remain available without the decision model or JavaScript. +- A failed suggestion opens the questions. If the suggestion button has focus, focus moves to the questions' disclosure before the button hides. +- Blocked storage reports that the entry was not saved and keeps the text available to copy. +- English and Québec French share the same fields. French uses vous. + +## Content fields + +All fields are escaped plain text. + +| Field | Meaning | +| --- | --- | +| `prompt` | Page title and accessible name of the writing area. | +| `questions` | Four `{ id, text }` optional questions, ordered `situation`, `action`, `next_step`, `when`. | +| `complete` | Completion message with a decorative check icon when all four details are found. | +| `support` | Support message. Replace it with your real support details. | +| `supportNote` | Support text always available below the questions. | +| `saved` | Confirmation that Save kept the entry in this tab. | +| `changed` | Message when the entry changed during a pending suggestion. | + +The proxy checks an assertiveness reflection. To change the task, change and evaluate its criteria too. Unknown fields, blank text and incorrect question order are rejected. + +## Logic + +Authored text is capped at 120 characters for titles and labels, 400 for questions and prompts, 300 for options, and 1,500 for answers, explanations and passage text. The schema gives each field's limit; `validateContent` enforces it. + +`logic.js` imports `journalDecision`, the criterion order and input limit from `proxy/logic/13-journal.js`. That module owns the confidence gates and returns decisions without text. Replace `complete` in the bilingual examples to change the completion message. An uncertain criterion counts as missing. The lower distress gate favours showing support over missing a need for help. + +| Export | Returns | +| --- | --- | +| `validateContent(content)` | Nothing; throws an `Error` naming the bad field. | +| `feedback(content, answers)` | `{ kind: 'support' \| 'complete', text }` or `{ kind: 'nudge', key, text }`. Invalid probabilities throw. | +| `validateAnswer(text)` | `{ ok: true, text }` or `{ ok: false, error: 'empty' \| 'tooLong' }`. Preserves whitespace. | +| `savedEntry(text, savedAt)` | `{ text, savedAt }`, or throws for invalid text or timestamp. | +| `validateState(value)` | A copied saved entry, or `null`. Accepts canonical UTC ISO timestamps and rejects extra fields. | +| `ANSWER_LIMIT` | The proxy-owned maximum entry length. | + +Pass `state: { read, write }` to store an entry and its save date. Only Save writes to storage. Invalid saved entries are ignored. A failed read blocks overwriting unreadable storage. A failed write reports failure. Without an adapter, Save reports that saving is unavailable. + +Repeated enhancement returns the same instance. `destroy()` cancels pending work, removes listeners and restores native questions and support text. The renderer accepts an optional `date: Date`; enhancement shows the saved entry’s local date after a restore or successful save, and local today before an entry exists. + +## Use it + +Copy this folder, `lib/`, `proxy/logic/13-journal.js` and `proxy/logic/shared.js`, preserving relative paths. Include `lib/base.css`, then `pattern.css`. Give each instance a unique id prefix. + +```js +import { render } from './patterns/journal/render.js'; +import { enhance } from './patterns/journal/enhance.js'; +import { strings } from './patterns/journal/strings.js'; +import { createAsk } from './lib/ask.js'; +import { browserState } from './lib/browser-state.js'; + +const markup = render(content, strings.en, { id: 'weekly', lang: 'en' }); +// Keeps the entry in this tab across reloads; denied storage makes Save say it couldn't keep it. +const state = browserState({ pattern: 'journal', lang: 'en', id: 'weekly' }); +const ask = createAsk({ endpoint: '/api/patterns', fetch: window.fetch.bind(window) }); +enhance(document.querySelector('[data-lp-pattern="journal"]'), { content, strings: strings.en, state, ask }); +``` + +The shared client calls `ask('13-journal', { answer }, { challengeSlot, signal })`. Its data notice appears under "Get a suggestion". Turnstile appears inline when a request needs clearance. The proxy never caches entries. Missing `ask`, failed configuration, invalid answers or failed requests open the authored questions. The demo sends no entry text and uses fixed answers. Its adapter writes to sessionStorage only on Save; closing the tab clears the entry. + +## Adapt it with your agent + +> Adapt the optional questions for [audience]. Keep the assertiveness reflection, four criterion IDs and their order. Keep authored questions and no scores. Use these real support details: [details]. Write English and Québec French together, using vous. + +## Licence + +[MIT](../../LICENSE). Keep the licence notice with copied code. diff --git a/docs/provenance/learning-patterns/patterns/live-feedback/README.md b/docs/provenance/learning-patterns/patterns/live-feedback/README.md new file mode 100644 index 0000000..c35ea17 --- /dev/null +++ b/docs/provenance/learning-patterns/patterns/live-feedback/README.md @@ -0,0 +1,113 @@ +--- +title: Feedback while you type +title_fr: De la rétroaction pendant que vous écrivez +summary: Write a classifiable response and see a checklist update when you pause. +section: question +ai: yes +offline: yes +learners: not tried +--- +# Feedback while you type + +Write three actions to become more assertive at work. A checklist updates when you pause. The decision model selects authored feedback to help you revise. + +## Use it when + +Use it for short action plans, outline-like writing assignments, and generally to give learners immediate feedback on responses that can be classified. This example checks for three different actions a colleague could see or hear, a time or situation, and your own commitments. The feedback helps you revise. + +## Avoid it when + +It gives no grade. + +## How it works + +1. Write at least 20 characters, excluding spaces at the start and end. A typing pause checks changed text with Perplexity Decider v1.1. +2. Read the checklist. Done items move to the top; the other items give hints for revision. Each group keeps criterion order. +3. Revise your plan. A short count is announced only when the number of done items changes. +4. When all four items are done, "Well done!" appears and automatic checks pause. The box stays editable. Your next edit resumes checks with the usual wait. "Well done!" hides if a later check counts fewer done items or the text falls below 20 characters. + +The wait is 2.5 times the median of the last nine inter-key gaps, between 400 and 900 ms. It defaults to 700 ms until three gaps are available. Question marks and Enter keep this wait for plans. + +All four items start with open dashed circles. Uncertain decisions keep the hint and circle. Done items have green checkmarks. They stay done while the last sentence is unfinished. A final period, exclamation mark, question mark or newline allows them to change. + +The quiet "Checking…" line appears during requests. Typing always stays available. Editing cancels the pending request and discards stale results. Unchanged text skips the check. + +All instances share 40 automatic checks per page session. At the limit, a calm message and native self-checks let you finish. Reloading resets the allowance. If "Well done!" shows when the limit is reached, it stays until your next edit opens the self-checks. A failed or refused check also opens the self-checks and announces "Automatic checking is unavailable. Tick the items in your plan." once, including on the last allowed check. Without JavaScript, the prompt, answer box and self-checks appear immediately. + +## Evidence + +Research on feedback supports specific, timely information for revision. The right timing depends on the task ([Shute, 2008](https://doi.org/10.3102/0034654307313795)). Linking an action to a time or situation draws on research about implementation intentions ([Gollwitzer, 1999](https://doi.org/10.1037/0003-066X.54.7.493)). These sources inform the checklist. They do not support the specific wait or 40-check allowance. This pattern and its feedback timing have not been tested with learners. + +[Dhakal et al., CHI 2018](https://doi.org/10.1145/3173574.3174220) measured a mean inter-key interval of 239 ms, and over 480 ms for slow typists, in a transcription task. [Nielsen, 1993](https://www.nngroup.com/articles/response-times-3-important-limits/) identifies about one second as the limit for uninterrupted flow. These inform the adaptive wait. They do not establish the multiplier, sample window or cap. Network time adds to the wait. + +Perplexity Decider v1.1 scored 11/12 on authored test answers using the original Jev question wording and confidence gates. This small test does not establish accuracy on learner answers or improved learning. The shared decision function keeps the original inclusive gates: found at 0.65 or above, missing at 0.35 or below, uncertain between them. + +## Accessibility + +Daily proxy caps show and announce the reason and midnight UTC reset once, keep focus in place and leave the fallback usable. Checks then stop until reload. + +Meets the shared baseline in the root README. + +- The list and checking line stay silent. The polite status region announces a changed count once. Reaching all four items announces "Well done! 4 of 4 done" once, and later checks that stay at four announce nothing. Focus stays in place. +- The box never locks, so typing after "Well done!" keeps every character. +- Checkmarks and open dashed circles show state through shape as well as colour. Each list item also has a screen-reader prefix, "Done:" or "To add:". Native fallback checkboxes expose their checked state. +- Reordering uses a 200 ms transform transition. Reduced motion moves items instantly. +- Native checkboxes work without JavaScript and after a failed check. +- English and Québec French share fields and controls. French uses vous. +- Browser checks cover keyboard use, axe, narrow reflow, text spacing, reduced motion and forced colours. Screen reader listening requires a separate human check. + +## Content fields + +| Field | Meaning | +| --- | --- | +| `prompt` | The learner's writing task. | +| `criteria` | Exactly four items, ordered `three_actions`, `observable`, `when`, `commitments`. | +| `criteria[].id` | Fixed question identity. | +| `criteria[].done` | The done statement and self-check checkbox label. | +| `criteria[].todo` | A short revision hint, used initially and for missing or uncertain decisions. Never supplies an answer. | + +All fields are escaped plain text. Unknown fields, empty strings and incorrect criterion IDs are rejected. Preserve what the proxy questions check. To change the topic, change and evaluate those questions too. + +## Logic + +Authored text is capped at 120 characters for titles and labels, 400 for questions and prompts, 300 for options, and 1,500 for answers, explanations and passage text. The schema gives each field's limit; `validateContent` enforces it. + +`logic.js` imports decisions, criterion IDs and the answer limit from `proxy/logic/02-live.js`. The browser and proxy share that pure module. + +| Export | Returns or value | +| --- | --- | +| `validateContent(content)` | Nothing; throws an `Error` naming a bad field. | +| `feedback(content, answers, draft = '', previous = [])` | `{ count, total, items }`; each item is `{ id, mark, text }`, with mark `done` or `todo`, ordered done first. Invalid decision answers throw. | +| `validateState(value)` | A clean `{ answer, ticked }`, or `null` for invalid saved values. | +| `ANSWER_LIMIT` | Proxy-owned 1,200-character cap. | +| `MIN_CHARS`, `AUTO_CHECK_LIMIT` | 20 trimmed characters, 40 automatic checks. | + +`lib/typing-pause.js` owns the adaptive wait. Live feedback uses `typingPause({ minChars: MIN_CHARS, questionMark: false })`, with `key(time)`, `wait()` and `delay(text, checked, enter = false)`. Delay returns milliseconds or `null` when no check is due. Live feedback does not request an immediate check on Enter. + +Pass `state: { read, write }` to save drafts and checklist ticks. Check results are not saved. Repeated enhancement returns the same instance. `destroy()` removes listeners and the "Well done!" line, cancels checks and restores the native checklist, keeping the draft and ticks. Re-enhancement keeps the remaining automatic allowance. + +## Use it + +Copy this folder, `lib/` and `proxy/logic/`, preserving relative paths. Include `lib/base.css` and then `pattern.css`. Give each instance a unique id prefix. Serve the proxy on the page's own origin. + +```js +import { render } from './patterns/live-feedback/render.js'; +import { enhance } from './patterns/live-feedback/enhance.js'; +import { strings } from './patterns/live-feedback/strings.js'; +import { createAsk } from './lib/ask.js'; +const markup = render(content, strings.en, { id: 'plan', lang: 'en' }); +// After the server markup reaches the browser: +enhance(document.querySelector('[data-lp-pattern="live-feedback"]'), { + content, strings: strings.en, ask: createAsk() +}); +``` + +The shared client calls `ask('02-live', { answer }, { challengeSlot, signal })` and reads `/config` for the data notice. Verification appears after a clearance refusal. Request or configuration failures open the self-check. Use Perplexity; other live providers select the self-check. The demo uses fixed feedback and sends no text. + +## Adapt it with your agent + +> Adapt the action plan for [audience]. Keep three different observable actions, at least one time or situation, and the learner’s own commitments. Keep the four criterion IDs, fields and authored feedback. Write English and Québec French together, using vous. Preserve the pause, unfinished-sentence rule, page allowance, completion pause, announcements, data notice and self-check. Evaluate any changed proxy questions before use. + +## Licence + +MIT. Keep the [licence notice](../../LICENSE) with copied code. diff --git a/docs/provenance/learning-patterns/patterns/misconception/README.md b/docs/provenance/learning-patterns/patterns/misconception/README.md new file mode 100644 index 0000000..0a6d8d6 --- /dev/null +++ b/docs/provenance/learning-patterns/patterns/misconception/README.md @@ -0,0 +1,103 @@ +--- +title: Spot the misconception +title_fr: Repérez l'idée fausse +summary: Answer a study question and compare feedback with a model answer. +section: question +ai: yes +offline: yes +learners: not tried +--- +# Spot the misconception + +Answer a study question. Compare your reasoning with the feedback and model answer. + +## Use it when + +Use it when a short answer can reveal a mistaken belief. This example asks whether rereading notes prepares you for a test. The proxy checks four study misconceptions. Feedback helps you revise your reasoning. + +## Avoid it when + +It gives no grade. + +## How it works + +1. Write an answer and select "Check my answer". Verification appears if needed. +2. A key idea gets a green check. A listed misconception gets its name and a correction. +3. "Not sure" shows a possible match for you to check. Unmatched answers get a prompt to compare. +4. The model answer opens with every result. Revise your answer and check again. +5. If automatic checking is unavailable, the button opens the model answer and misconception checklist. Tick beliefs in your answer and use the corrections to revise. + +## Evidence + +Refutation texts produced an average learning benefit of 0.41 standard deviations across 44 comparisons involving 3,869 participants ([Schroeder and Kucera, 2022](https://doi.org/10.1007/s10648-021-09656-z)). This supports naming a mistaken idea and explaining the correction. A separate review found larger average effects for explanatory computer feedback than for right-or-wrong feedback ([Van der Kleij et al., 2015](https://doi.org/10.3102/0034654314564881)). The decision model selects authored explanations and was tested only on agent-written answers. Its identification of misconceptions and the learning benefits of this feedback have not been tested with learners. + +## Accessibility + +Daily proxy caps show and announce the reason and midnight UTC reset once, keep focus in place and leave the fallback usable. + +The question labels the three-line answer box. Submitting announces feedback once through one status region, without moving focus. While a check runs, the button keeps its label and focus, and pressing it again sends nothing. "Checking your answer…" appears beside it, and the status region says it once if the check takes more than a second. Icons accompany text; colour alone conveys no meaning. Native disclosures and checkboxes support keyboards. Shared styles support reduced motion, forced colours and narrow layouts. Without JavaScript, the answer box, open model answer and checklist appear immediately. English and Québec French share the content structure. + +The browser tests cover all results, fallback, axe, keyboard use, 320 px reflow, text spacing and lifecycle in three engines. Human screen-reader checks remain separate. + +## Content fields + +| Field | Meaning | +| --- | --- | +| `question` | The quiz question and answer-box label. | +| `keyIdea` | Feedback when the expected idea is found. | +| `model` | The authored model answer. | +| `unsureKeyIdea` | Comparison prompt when the key idea is uncertain. | +| `unsureMisconception` | Authored template containing `{idea}` and `{why}`. | +| `noMatch` | A prompt to compare unmatched reasoning with the model answer. | +| `misconceptions` | Exactly four entries, in order: `rereading`, `highlighting`, `cramming`, `watching`. | +| `misconceptions[].id` | The proxy's fixed belief identity. | +| `misconceptions[].label` | A short name for the wrong belief. | +| `misconceptions[].idea` | The belief as a clause that fits the unsure template. | +| `misconceptions[].why` | Correction and an action to try instead. | + +Text must be nonempty. The schema and validator reject unknown fields, missing template tokens and incorrect belief IDs or order. Preserve the beliefs the proxy checks. To change the topic, update and evaluate its questions. + +## Logic + +Authored text is capped at 120 characters for titles and labels, 400 for questions and prompts, 300 for options, and 1,500 for answers, explanations and passage text. The schema gives each field's limit; `validateContent` enforces it. + +The pure module imports identities, answer length and the decision model's confidence gate from `proxy/logic/06-misconceptions.js`. Keep that module and `shared.js` when copying this pattern. + +| Export | Returns | +| --- | --- | +| `validateContent(content)` | Nothing; throws an `Error` naming the bad field. | +| `feedback(content, answers, model?)` | `{ kind, heading, text }`. Kinds are `correct`, `misconception`, `unsure-key`, `unsure-misconception`, `none`. Without a decision model, uses Jev's confidence gate, also used by the default Perplexity provider. Invalid labels or probabilities throw. | +| `validateState(value)` | A clean `{ answer, ticked }` draft, or `null`. | +| `ANSWER_LIMIT` | The proxy-owned answer limit. | + +`answers` is `{ misconception: { choice, confidence } }`. A no-match label uses the comparison prompt at every confidence. Known beliefs and the key idea use the configured decision model's gate. Only authored strings reach the learner. + +Pass `state: { read, write }` to save drafts and checklist ticks. Check results are not saved. Editing during a check discards its result. Repeated enhancement returns the same instance. `destroy()` removes listeners, cancels checks and restores the native fallback. + +## Use it + +Copy this folder, `lib/` and the two proxy logic modules, preserving relative paths. Include `lib/base.css`, then `pattern.css`. Give each instance a unique id prefix. Serve the proxy on the page's origin. + +```js +import { render } from './patterns/misconception/render.js'; +import { enhance } from './patterns/misconception/enhance.js'; +import { strings } from './patterns/misconception/strings.js'; +import { createAsk } from './lib/ask.js'; +// Server, with the English example loaded as content: +const markup = render(content, strings.en, { id: 'study', lang: 'en' }); +// Browser: +const ask = createAsk({ endpoint: '/api/patterns', fetch: window.fetch.bind(window) }); +enhance(document.querySelector('[data-lp-pattern="misconception"]'), { content, strings: strings.en, ask }); +``` + +The shared `ask(block, fields, { challengeSlot, signal? })` client returns typed answers. Its cached `config()` supplies the provider, decision model, bilingual data notice and public verification key. The notice appears under the answer box. Turnstile loads after a clearance refusal. Missing `ask`, failed configuration or a failed check opens the self-check. The demo uses fixed feedback and sends no text. + +This pattern collects and stores no answers. A host may collect unmatched answers for review with explicit consent, separate storage and a retention policy. The learner page shows no review inbox, raw decision model output or request timing. + +## Adapt it with your agent + +> Adapt this example for [audience]. Keep familiarity versus recall and the four beliefs about rereading, highlighting, cramming and watching. Keep the fields and belief order. Explain each correction and what to do instead. Give uncertain results a comparison prompt. Write English and Québec French together, using vous and « guillemets » with non-breaking spaces. Preserve authored feedback, the data notice, proxy gates, on-demand verification and offline self-check. Evaluate any changed proxy questions before use. + +## Licence + +MIT. Keep the [licence notice](../../LICENSE) with copied code. diff --git a/docs/provenance/learning-patterns/patterns/retrieval-sheet/README.md b/docs/provenance/learning-patterns/patterns/retrieval-sheet/README.md new file mode 100644 index 0000000..1dddfbd --- /dev/null +++ b/docs/provenance/learning-patterns/patterns/retrieval-sheet/README.md @@ -0,0 +1,172 @@ +--- +title: Printable retrieval sheet +title_fr: Feuille de rappel à imprimer +summary: Print questions and answers on opposite sides of a sheet, then test yourself on a chosen date. +section: question +ai: no +offline: yes +learners: not tried +--- +# Printable retrieval sheet + +Print questions on the front and answers on the back. Choose a date to answer from memory and check your work. + +## Use it when + +Use it for a short module with a few ideas or actions worth recalling. Keep questions focused and answers short. + +Learners can use the sheet without internet. Print it or save a PDF while connected. A SCORM package prints from its course files without fetching anything. + +## Avoid it when + +Choosing a date sends no reminder. Arrange one through your course host if needed. + +## How it works + +1. Choose when to test yourself: in 2 days, 1 week, 2 weeks or 1 month. Each choice shows its date. The default is 1 week. Choose "Another date" to enter your own date. +2. Select "Front" to preview questions or "Back" to preview answers. +3. Select "Print the sheet". Questions print first, then answers. +4. On your chosen date, write answers from memory. Check the back and revisit what you missed. + +## Write this in .prax + +In `.prax`, a `retrieval-sheet` block has a title and one to eight items. Start each question with `::` and give it exactly one `answer:`. Studio creates the IDs; authors do not write them. + +In Studio, the learner chooses a review date from presets or a date field. The date and sheet side stay only on the page and reset on reload. Choosing a date sends no reminder. The sheet prints questions first, then answers. Nothing is scored or reported. + +English (Praxity Studio from commit `8cad2f0`, 2026-10-19): + +```prax +--- +lang: en +--- +# The water cycle + +::: retrieval-sheet Changes of state +:: What is evaporation? +answer: Liquid water changing into water vapour. +:: What is condensation? +answer: Water vapour changing into liquid water. +::: +``` + +French (Praxity Studio from commit `8cad2f0`, 2026-10-19): + +```prax +--- +lang: fr +--- +# La Lune + +::: retrieval-sheet Lumière et mouvement +:: Pourquoi la Lune brille-t-elle ? +answer: Elle réfléchit une partie de la lumière du Soleil. +:: Que signifie « orbite » ? +answer: C’est le trajet d’un astre autour d’un autre. +::: +``` + +## Evidence + +Recalling studied prose improved retention two days and one week later compared with restudying ([Roediger and Karpicke, 2006](https://doi.org/10.1111/j.1467-9280.2006.01693.x)). A meta-analysis found that practice tests beat restudying and other study conditions ([Adesope, Trevisan and Sundararajan, 2017](https://doi.org/10.3102/0034654316689306)). Recall tests, like this sheet, produced larger benefits than recognition tests ([Rowland, 2014](https://doi.org/10.1037/a0037559)). + +The best gap before review grows with how long you need to remember ([Cepeda et al., 2008](https://doi.org/10.1111/j.1467-9280.2008.02209.x)). The seven-day default suits remembering for weeks. Answers on the back give feedback after each attempt. + +Use remains uncertain. Students tend to study when deadlines push them ([Hartwig and Dunlosky, 2012](https://doi.org/10.3758/s13423-011-0181-y)). Choosing a date sends no reminder, and this sheet has not been tested with learners. + +## Accessibility + +Follows the shared baseline in the root README. + +- Front and Back tabs share one tab stop. Left and Right arrows change sides; Home selects Front and End selects Back. Selection updates the tabs and panel before moving focus. +- A fieldset names the five spacing choices. Each radio's name includes its date, after a comma. Native radios support Tab and arrow keys, with a visible selected row and keyboard focus. +- "Another date" reveals a field labelled "Date" and announces the date in it. Date changes and printing announce once and keep focus. +- A newly invalid date announces its error once through the status region. The field gets the error as its description when focus leaves it, so it isn't read twice. Repeated invalid edits stay silent. A valid date, even the same one as before, announces once. A year typed digit by digit changes nothing until it has four digits. +- Tab selection identifies the active side without another status announcement. +- NVDA speaks a newly selected tab twice after Arrow, Home or End changes sides in Chrome and Firefox. The W3C ARIA Authoring Practices example also does this. Tabs still activate on focus; requiring Enter or Space would add a step for everyone. +- Printed questions leave space for handwritten answers. Check printer margins and double-sided settings by hand. + +## Content fields + +Authors write plain text. Rendering escapes HTML characters. + +| Field | Meaning | +| --- | --- | +| `title` | Module title in the scene header and on both printed sides. The host owns `h1`; the scene uses `h2`. | +| `questions` | At least one question and answer, in sheet order. | +| `questions[].id` | Unique id using letters, digits, underscores or hyphens. | +| `questions[].question` | Front-side recall question, with writing space. | +| `questions[].answer` | Author's answer at the same number on the back. | + +`validateContent` rejects empty strings, unknown fields, invalid ids and duplicates. Whitespace is allowed. The schema's `x-uniqueBy` annotation needs the validator's uniqueness check. + +English and Québec French strings share `instruction`, `dateLabel`, `dateError`, `dateChanged`, `sides`, `front`, `back`, `questions`, `answers`, `backInstruction`, `print`, `printHint` and `printing`. The spacing choices use `spacingLegend`, `in2Days`, `in1Week`, `in2Weeks`, `in1Month` and `anotherDate`. The custom field uses `customDateLabel`; `dateLabel` stays on the printed sheet. Only `dateChanged` uses `{date}`. Authors write every learner-facing message. + +## Logic + +Authored text is capped at 120 characters for titles and labels, 400 for questions and prompts, 300 for options, and 1,500 for answers, explanations and passage text. The schema gives each field's limit; `validateContent` enforces it. + +`validateContent` and the schema cap `questions` at eight. + +`logic.js` has no DOM code. Hosts can use these exports with their own interface. + +| Export | Returns | +| --- | --- | +| `validateContent(content)` | Nothing. Throws an `Error` naming the first bad field. | +| `presetDays` | The spacing offsets `[2, 7, 14, 30]` in civil days. | +| `dateAfterDays(today, days)` | `YYYY-MM-DD`, counting whole days from the local date of `today` with UTC arithmetic. Leaves `today` unchanged. Invalid dates, non-integer offsets and unsupported years throw. | +| `defaultDate(today)` | Seven local calendar days ahead as `YYYY-MM-DD`. Leaves `today` unchanged. Invalid dates and unsupported years throw. | +| `isDate(value)` | Whether the value is a real `YYYY-MM-DD` civil date in years 0001 to 9999. | +| `isPartialYear(value)` | Whether a date field value has a year under 1000, which a browser can report while the year is still being typed. | +| `formatDate(date, lang)` | Readable date using `en-CA` or `fr-CA`. UTC formatting preserves the civil day across time zones. Invalid dates throw. | +| `formatShortDate(date, lang)` | Short weekday and date using `en-CA` or `fr-CA`, such as "Thu, Oct 15" or "jeu. 15 oct.". UTC formatting preserves the civil day. Invalid dates throw. | +| `validateState(value)` | Copied `{ date, side }`, or `null` for invalid state. | + +The spacing choices offer several gaps because the best gap grows with how long you need to remember, as the evidence above describes. Seven days is a demo default, not an established optimum for every topic or learner. Scheduling handles month boundaries, leap years and daylight-saving changes. + +The preview has A4 proportions, a paper edge and a soft shadow. It grows for narrow screens or enlarged text. The scene shows a file-text icon and module title. The recall instruction appears once on the front, including in print. Colours and fonts use shared `--lp-*` tokens. + +Without JavaScript, both sides appear in order with the server's date. Controls stay hidden. Use the browser's Print command. + +The examples print as two black-on-white pages on A4 or Letter, without controls or site navigation. Choose double-sided printing with the long-edge flip for one sheet. Turn off browser headers and footers to omit URLs or page numbers. A sheet accepts at most eight questions. The print tests cover eight questions on both paper sizes in both languages. Long stems, answers or deliberate line breaks can still add pages; text is never clipped. Check print preview after editing questions. + +`render` accepts optional `today: Date` for a reproducible build date. Enhancement uses the learner's local day, so a static build date does not determine the interactive default. + +Optional `state: { read, write }` stores `{ date, side }`. `date` is a valid `YYYY-MM-DD` civil date; `side` is `front` or `back`. Invalid state is ignored as a whole. Valid dates restore exactly and silently, including past dates. A date matching a spacing choice from today selects that row. Other dates select "Another date" and fill its field. Each change writes a fresh copy; host mutation cannot change the live sheet. Host errors propagate. + +Invalid or empty edits in "Another date" show a local error and disable Print. Choosing a preset clears the error and enables Print. The sheet and state keep the last valid date until a valid edit. Past dates are allowed. + +Repeated enhancement preserves server elements and returns the same instance. `destroy()` removes listeners, hides controls and restores server dates and both visible sides. Repeated destruction is safe; the root can then be enhanced again. + +The print stylesheet hides everything outside sheet roots and keeps their ancestors in flow. Print buttons target their own instance until `afterprint`. The browser's Print command prints all sheet instances in document order. Load this stylesheet only on pages where this print view is wanted. + +Saving a date sends no reminder. Printing does not show that the learner used the sheet. + +## Use it + +Copy `patterns/retrieval-sheet/` and `lib/`, keeping their relative paths. Link `lib/base.css`, then `patterns/retrieval-sheet/pattern.css`. Use a unique id prefix per instance. Render on the server, then enhance in the browser. + +```js +import { render } from './patterns/retrieval-sheet/render.js'; +import { enhance } from './patterns/retrieval-sheet/enhance.js'; +import { strings } from './patterns/retrieval-sheet/strings.js'; + +const content = { + title: 'Take a time-out', + questions: [{ id: 'pause', question: 'What makes a pause a time-out?', + answer: 'Say you need a few minutes, then return to the conversation.' }] +}; +const markup = render(content, strings.en, { id: 'take-home', lang: 'en' }); +// Send markup to the browser, then: +const instance = enhance(document.querySelector('[data-lp-pattern="retrieval-sheet"]'), { + content, strings: strings.en +}); +``` + +## Adapt it with your agent + +> Write a retrieval sheet for [topic] and [audience]. Keep `{ title, questions: [{ id, question, answer }] }` with stable, unique ids. Write focused questions and short answers. Preserve the spacing radio group, the "Another date" field, Front and Back tabs above the preview, writing space, no-JavaScript view and two-sided printing. Write English and Québec French together, using vous. Authors write every message. Preserve render, enhancement, state, CSS tokens and accessibility. Update examples and tests. Check A4 and Letter print previews. Show both languages for review. + +## Licence + +MIT. Keep the [licence notice](../../LICENSE) with copied code. diff --git a/docs/provenance/learning-patterns/patterns/review-prompts/README.md b/docs/provenance/learning-patterns/patterns/review-prompts/README.md new file mode 100644 index 0000000..627008b --- /dev/null +++ b/docs/provenance/learning-patterns/patterns/review-prompts/README.md @@ -0,0 +1,124 @@ +--- +title: Questions inside the reading +title_fr: Questions au fil de la lecture +summary: Answer questions between reading sections, compare with the answers and see when to review. +section: reading +ai: no +offline: yes +learners: not tried +--- +# Questions inside the reading + +Answer questions between reading sections, compare with the answers and see when to review. + +## Use it when + +Use it for short readings with facts or actions learners need to recall later. Give each section one focused question and a short answer. + +## Avoid it when + +Ratings record what learners say they remembered. They do not verify recall or workplace performance. + +## How it works + +1. Read a section and answer its question from memory. +2. Select "I have my answer" to reveal the answer and compare it with yours. +3. Choose "I remembered" or "I forgot" to see your next review date. Change your choice to update the date. +4. Check your progress count and continue to the next section, if there is one. + +## Evidence + +Trying to recall what you read helps you remember it later better than rereading ([Roediger and Karpicke, 2006](https://doi.org/10.1111/j.1467-9280.2006.01693.x)). Practice testing was one of only two techniques a major review rated highly useful ([Dunlosky et al., 2013](https://doi.org/10.1177/1529100612453266)). Spacing reviews helps too. The best gap grows with how long you need to remember ([Cepeda et al., 2006](https://doi.org/10.1037/0033-2909.132.3.354)). + +Questions inside a text help readers learn the material they ask about. Effects on other material are weaker ([Hamaker, 1986](https://doi.org/10.3102/00346543056002212)). Quantum Country's essays with spaced review questions inspired the design ([Matuschak and Nielsen, 2019](https://numinous.productions/ttft/)). + +Students who overrate their recall stop studying too soon ([Dunlosky and Rawson, 2012](https://doi.org/10.1016/j.learninstruc.2011.08.003)), so learners see the answer before rating their recall. The review gaps are preset. This pattern has not been tested for recall after a delay. + +## Accessibility + +Follows the shared baseline in the root README. + +- The article title and section headings form the reading's heading structure. "I have my answer" reveals a region labelled "Answer" and focuses it. +- Rating buttons keep their visible labels. The chosen rating has a pressed state. +- Each rating announces its review date and keeps focus on the chosen button. Revealing answers and updating progress stay silent. +- Without JavaScript, "Show the answer" opens a native disclosure. + +## Content fields + +Content is plain text. Rendering escapes HTML characters. + +| Field | Meaning | +| --- | --- | +| `title` | Required article title, rendered as `h2`. The host owns `h1`. | +| `parts` | At least one reading part, in display order. | +| `parts[].id` | Unique id using letters, digits, underscores or hyphens. Keep it stable for saved state. | +| `parts[].heading` | Part heading, rendered as `h3` in the body font. | +| `parts[].paragraphs` | At least one nonempty paragraph. | +| `parts[].question` | Retrieval prompt below the reading. | +| `parts[].answer` | Answer shown after recall, or in a native disclosure without JavaScript. | +| `reviewDays.remembered` | Positive whole calendar days after "I remembered". | +| `reviewDays.forgot` | Positive whole calendar days after "I forgot". | + +`validateContent` rejects empty strings, unknown fields and duplicate part ids. Whitespace is allowed. It supplies the uniqueness check that ordinary JSON Schema tools need, recorded as `x-uniqueBy`. + +Strings in both languages share the keys `readingTime`, `progress`, `commit`, `answer`, `show`, `remembered`, `forgot` and `nextReview`. Placeholders are `{n}` for reading time, `{count}` and `{total}` for progress, and `{date}` for the review date. + +The examples have one section about stonewalling. You can add parts, each with a question, answer and review date. Check subject-matter claims before publishing. + +## Logic + +Authored text is capped at 120 characters for titles and labels, 400 for questions and prompts, 300 for options, and 1,500 for answers, explanations and passage text. The schema gives each field's limit; `validateContent` enforces it. + +`logic.js` has no DOM code. Hosts can build their own interface. + +| Export | Returns | +| --- | --- | +| `validateContent(content)` | Nothing. Throws an `Error` naming the first bad field. | +| `readingMinutes(content)` | Whole minutes at 200 words per minute, rounded up with a minimum of one. Validates content first. | +| `scheduleReview(date, result, reviewDays?)` | A new `Date` at local midnight after the configured calendar days. Defaults to three days for `remembered` and one for `forgot`. | +| `isoDate(date)` | The local calendar date as `YYYY-MM-DD`. | +| `validateState(content, value)` | A copied `{ results: { [partId]: { result, reviewOn } } }`, or `null` for invalid saved state. | +| `REVIEW_DAYS` | A frozen `{ remembered: 3, forgot: 1 }` default. | + +Progress counts questions rated, including saved ratings. Opening an answer adds nothing; changing a rating counts no extra question. Progress updates stay silent. + +`readingMinutes(content)` counts whitespace-separated words in the title, headings, paragraphs, questions and answers. It excludes interface labels. + +Without JavaScript, the title, reading time, sections, questions and native answers remain usable. Ratings and progress stay hidden. + +The enhancer passes `content.reviewDays` to `scheduleReview` and starts each interval from the day the learner chooses a rating. + +Unsupported results, invalid dates and dates outside years 0000 to 9999 throw. Day counts must be positive safe integers, but very large counts can still exceed the date range. + +Display dates use `Intl.DateTimeFormat` with `en-CA` or `fr-CA` for weekday, day, month and year. Saved dates are calendar dates. Changing time zones does not shift their day. + +Pass `state: { read, write }` to save `{ results: { [partId]: { result, reviewOn } } }`. `result` is `remembered` or `forgot`; `reviewOn` is a valid `YYYY-MM-DD` date. Invalid state is ignored as a whole. Valid dates restore exactly, even when overdue. Rated parts open silently. Each choice saves an independent copy of all results. `destroy()` removes listeners and ratings, keeping native answers usable. Repeated enhancement returns the same instance. Repeated destruction is safe. + +The host can use `reviewOn` for reminders; the pattern sends none. Returning to a SCORM package requires email, a message or an xAPI target connected to a reminder service. Saving a date or sending an xAPI statement alone does not arrange a return visit. + +## Use it + +Copy `patterns/review-prompts/` and `lib/`, preserving their relative paths. Link `lib/base.css`, then `patterns/review-prompts/pattern.css`. Use a unique id prefix per instance. Render on the server before calling `enhance` in the browser. + +```js +import { render } from './patterns/review-prompts/render.js'; +import { enhance } from './patterns/review-prompts/enhance.js'; +import { strings } from './patterns/review-prompts/strings.js'; +const content = { + title: 'Stonewalling and time-outs', + parts: [{ id: 'pause', heading: 'Take a time-out', + paragraphs: ['Announce your pause, then return to the conversation.'], + question: 'What makes a pause a time-out?', answer: 'Announce it and return.' }], + reviewDays: { remembered: 3, forgot: 1 } +}; +document.querySelector('main').innerHTML = render(content, strings.en, { id: 'reading', lang: 'en' }); +const instance = enhance(document.querySelector('[data-lp-pattern]'), { content, strings: strings.en }); +``` + +## Adapt it with your agent + +> Rewrite this article for [topic] and [audience]. Keep `{ title, parts: [{ id, heading, paragraphs, question, answer }], reviewDays: { remembered, forgot } }`. Write a short title, a focused question and a concise answer per part. Use stable unique ids. Keep the recall button and answer focus, native disclosures without JavaScript, h2 title, h3 section headings, silent progress, editable self-ratings and calendar-day scheduling. Include no score. Keep English and Québec French together; use vous in French. Authors write every message. Follow the pattern contract, including state, accessibility and CSS tokens. Update examples and tests. Show both languages for review. Explain how the host will arrange reminders. + +## Licence + +MIT. Keep the [licence notice](../../LICENSE) with copied code. diff --git a/docs/provenance/learning-patterns/patterns/self-check/README.md b/docs/provenance/learning-patterns/patterns/self-check/README.md new file mode 100644 index 0000000..54d1a26 --- /dev/null +++ b/docs/provenance/learning-patterns/patterns/self-check/README.md @@ -0,0 +1,114 @@ +--- +title: Check your own answer +title_fr: Vérifiez votre réponse +summary: Write an answer, tick the parts it includes, then compare with a model and hints. +section: question +ai: no +offline: yes +learners: not tried +--- +# Check your own answer + +Write an answer, tick the parts it includes, then compare with a model. Hints help you add missing parts. + +## Use it when + +Use it when learners can check their answers against a short list of clear parts. It works offline and can help you compare self-checking with automated feedback. + +## Avoid it when + +The pattern does not judge the learner's text. + +## How it works + +1. Write your answer and select "Check my answer". Email tasks also show a recipient and subject. +2. Tick each part your answer includes. A count updates as you tick. +3. Select "Show feedback" to compare your answer with the model. Numbered marks connect the model to the checklist. +4. Read "Included" or "To add" beside each part, with a hint for each part to add. +5. Edit your answer or ticks and submit again. Select "Start over" to clear them. + +## Evidence + +Adults checked their recall against a list of key ideas. They judged their learning more accurately and did better on a later test ([Rawson, O'Neil and Dunlosky, 2011](https://doi.org/10.1037/a0024749)). Seeing the correct answer made middle school students less sure of their wrong answers ([Lipko et al., 2009](https://doi.org/10.1037/a0017599)). + +Self-assessment against clear criteria helps learners improve drafts. It is not meant for grading ([Andrade and Valtcheva, 2009](https://doi.org/10.1080/00405840802577544)). A meta-analysis of 175 studies found that it improved academic performance ([Yan et al., 2022](https://doi.org/10.1016/j.edurev.2022.100484)). + +Explanations helped more than right-or-wrong feedback in computer-based learning ([Van der Kleij, Feskens and Eggen, 2015](https://doi.org/10.3102/0034654314564881)), so each missing part has a hint. The model appears after an attempt. Easy access to answers can raise practice scores without lasting learning ([Bastani et al., 2025](https://doi.org/10.1073/pnas.2422633122)). + +This sequence has not been tested as a whole. Ticks record only the learner's own judgment. + +## Accessibility + +Follows the shared baseline in the root README. + +- An empty answer gets an error linked to its text box. Checking moves focus to the first checklist item. +- Native checkbox labels supply their names and support clicking. Firefox may also announce "clickable" on these labels. +- Numbered marks connect the model to the checklist. Each part has a visible status and each missed part has a hint. +- Feedback announces how many parts the learner ticked. Start over focuses the answer field. + +## Content fields + +Content is plain text. Rendering escapes HTML characters. + +| Field | Meaning | +| --- | --- | +| `task` | The writing task. | +| `context` | Optional email context. Omit it to hide recipient and subject fields. | +| `context.to` | Email recipient, required with context. | +| `context.initials` | Decorative recipient initials, required with context. | +| `context.subject` | Email subject, required with context. | +| `context.placeholder` | Optional placeholder text. Allows line breaks; never saved as an answer. | +| `parts` | At least one part, in display order. | +| `parts[].id` | Unique id using letters, digits, underscores or hyphens. | +| `parts[].label` | The checkbox label and the part's name in feedback. | +| `parts[].missed` | The hint when the learner leaves this part unticked. | +| `parts[].evidence` | Exact text that occurs once in the model, or `null` for a whole-answer part. | +| `model` | The model answer. | + +Required strings must be nonempty. Context requires `to`, `initials` and `subject`. It cannot be `null`, `{}` or incomplete. The optional placeholder must be a string; it may be empty. `validateContent` rejects unknown fields and duplicate ids. It checks unique ids and evidence matching, recorded as `x-uniqueBy` and `x-occursOnceIn` in the schema. Ordinary JSON Schema tools need these checks from `validateContent`. + +## Logic + +Authored text is capped at 120 characters for titles and labels, 400 for questions and prompts, 300 for options, and 1,500 for answers, explanations and passage text. The schema gives each field's limit; `validateContent` enforces it. + +`logic.js` has no DOM code. Hosts such as Praxity Studio can build their own interface. + +| Function | Returns | +| --- | --- | +| `validateContent(content)` | Nothing. Throws an `Error` naming the first bad field. | +| `feedback(content, ticked)` | `{ count, total, items }`. Each item is `{ id, included, label, hint }`; `hint` is `null` for an included part and the authored `missed` text otherwise. | +| `annotate(model, parts, includedIds)` | `{ text, partIndex, included }[]` in model order. Unmarked text has `partIndex: null` and `included: false`. Part indexes refer to author order. Pass validated content and an array of included ids. If evidence overlaps, the first span in model order owns the text. Whole-answer parts have no mark. | +| `validateState(content, value)` | A clean `{ answer, ticked, shown }`, or `null` when `value` isn't valid saved state. | + +Without JavaScript, open "Check for these parts." to read all hints and the model answer. + +Pass `state: { read, write }` to save `{ answer, ticked, shown }`. Invalid saved values are ignored. A nonempty draft opens the checklist. Saved results rebuild from ticks without an announcement. Editing keeps the old result visible until you submit again. `instance.destroy()` removes listeners and restores the native fallback. + +## Use it + +Copy `patterns/self-check/` and `lib/`, keeping their relative paths. Link `lib/base.css`, then `patterns/self-check/pattern.css`. Use a unique id prefix per instance. The example combines both calls; render HTML on the server before calling `enhance` in the browser. + +```js +import { render } from './patterns/self-check/render.js'; +import { enhance } from './patterns/self-check/enhance.js'; +import { strings } from './patterns/self-check/strings.js'; +const content = { + task: 'Explain why you need another day.', + parts: [{ id: 'reason', label: 'Reason', missed: 'Explain the delay.', evidence: 'The data arrived late.' }], + model: 'The data arrived late. Could I have until Tuesday?' +}; +document.querySelector('main').innerHTML = render(content, strings.en, { id: 'practice', lang: 'en' }); +const instance = enhance(document.querySelector('[data-lp-pattern]'), { content, strings: strings.en }); +``` + +For an email task, add `context: { to: 'Sam', initials: 'S', subject: 'Report' }`. Use `context.placeholder` for a prompt in the empty field. Both example files show email tasks. + +## Adapt it with your agent + +Copy this prompt and fill in your topic and audience. + +> Rewrite the self-check examples for [topic] and [audience]. Ask for an answer learners could use at work. Keep `{ task, parts: [{ id, label, missed, evidence }], model }`. Email tasks may add `context: { to, initials, subject }` and an optional `placeholder`. Use unique part ids. Evidence must occur exactly once in the model, or be null for a whole-answer part. Write clear part labels and specific hints. Keep English and Québec French together; use vous in French. A model may only choose authored messages. Follow the pattern contract, including state, accessibility and CSS tokens. Update examples and tests. Show both languages for review. + +## Licence + +MIT. Keep the [licence notice](../../LICENSE) with copied code. diff --git a/docs/provenance/learning-patterns/patterns/test-out/README.md b/docs/provenance/learning-patterns/patterns/test-out/README.md new file mode 100644 index 0000000..cd50072 --- /dev/null +++ b/docs/provenance/learning-patterns/patterns/test-out/README.md @@ -0,0 +1,118 @@ +--- +title: Pre-test to skip sections +title_fr: Sautez certaines sections après un test +summary: Answer a short check to see which refresher sections you can skip and which to take. +section: course +ai: no +offline: yes +learners: not tried +--- +# Pre-test to skip sections + +Answer a short check to see which course sections you can skip and which to take. + +## Use it when + +Use it for a small refresher where one or two questions can justify skipping a section. Advanced questions must test any prerequisite knowledge they credit. + +## Avoid it when + +Keep all sections required when the course needs proof that learners saw each one. The host course handles navigation and completion records. + +## How it works + +1. Read the course outline and select "Start the check". +2. Answer each question. The counter shows the question number and section. Select "Next" to continue or "Back" to revisit an answer. +3. After the last question, select "Check my answers". +4. Read your course plan. Each section says "Skip" or "Take it". +5. Open "Review answers" to see your marks, correct answers and explanations for missed questions. +6. Select "Start over" to clear your answers and return to the outline. + +## Evidence + +Instruction that helps beginners can hinder learners who already know the material. This is the expertise reversal effect ([Kalyuga et al., 2003](https://doi.org/10.1207/S15326985EP3801_4)). Short tests can estimate what learners know. In experiments, a rapid test correlated up to 0.92 with longer tests and helped choose instruction ([Kalyuga and Sweller, 2004](https://doi.org/10.1037/0022-0663.96.3.558)). + +A correct advanced answer can also credit the knowledge it builds on. This follows knowledge space theory, the basis of ALEKS ([Doignon and Falmagne, 1985](https://doi.org/10.1016/S0020-7373(85)80031-6)), and Math Academy's diagnostic ([Math Academy](https://mathacademy.com/how-our-ai-works)). Mastery learning programs require evidence of mastery before learners move on. They improved exam performance in a meta-analysis of 108 evaluations ([Kulik, Kulik and Bangert-Drowns, 1990](https://doi.org/10.3102/00346543060002265)). + +One or two multiple-choice answers give little evidence; a guess can be right. Check that every question tests the knowledge it credits. This check and its prerequisite links have not been tested with learners. + +## Accessibility + +Follows the shared baseline in the root README. + +- Each question has a fieldset, legend and labelled options. Missing-answer errors link to each option, so screen readers read them once. +- Panel changes focus the heading to announce progress once. Validation errors keep focus on the action button. +- Results announce the skip summary. Section statuses and answer marks use words and icons. +- Review answers uses a native disclosure. Reset focuses the outline heading. Saved state restores silently. + +## Content fields + +Authors write plain text. Rendering escapes HTML characters. Keep English and Québec French examples together. + +| Field | Meaning | +| --- | --- | +| `title` | Course title in the scene header. | +| `allowTestOut` | Required boolean. `false` keeps every section required. | +| `sections` | Nonempty outline in display order. | +| `sections[].id` | Unique positive safe integer. Lowest numeric id breaks credit ties. | +| `sections[].title` | Title in the outline, question counter and result plan. | +| `sections[].requires` | Unique existing section ids. Cycles and self-links are rejected. | +| `questions` | One or two questions per section. Rendering groups them in outline order. | +| `questions[].id` | Unique string id using letters, digits, underscores or hyphens. | +| `questions[].section` | Existing numeric section id. | +| `questions[].text` | Fieldset legend. | +| `questions[].options` | At least two choices. | +| `questions[].options[].id` | Unique within the question. Letters, digits, underscores or hyphens. | +| `questions[].options[].text` | Choice label. | +| `questions[].correct` | Id of the correct option. | +| `questions[].explanation` | Answer explanation after a wrong answer and in native Answers. Does not explain prerequisite credit. | + +`validateContent` rejects unknown fields, blank text, duplicate ids, bad references and cyclic prerequisites. The schema describes local fields. Its `x-uniqueBy`, `x-acyclicReferences`, `x-sectionReference`, `x-sectionQuestions` and `x-optionReference` annotations need `validateContent`. Ordinary schema validators do not enforce them. + +## Logic + +Authored text is capped at 120 characters for titles and labels, 400 for questions and prompts, 300 for options, and 1,500 for answers, explanations and passage text. The schema gives each field's limit; `validateContent` enforces it. + +`logic.js` and `strings.js` have no DOM code or runtime dependencies. + +| Export | Returns | +| --- | --- | +| `validateContent(content)` | Nothing. Throws an `Error` naming the bad field. | +| `plan(sections, passed, allowTestOut = true, failed = [])` | `{ rows, skip }`. Rows follow outline order and contain `{ id, title, action: 'take' \| 'passed' \| 'credited', by? }`. `by` names the section granting credit. Invalid sections, passed or failed ids, overlapping pass/fail ids and settings throw. | +| `score(content, picks)` | `{ right, wrong, unanswered, passed, failed, rows, skip }`. Question ids follow content order; `passed` and `failed` section ids follow outline order. A wrong answer directly fails its section. Partial picks can pass fully answered sections. The enhancer requires complete picks. Invalid picks throw. | +| `validateState(content, value)` | Copied `{ picks, shown, step }` or `null`. Step 0 is the outline, 1 through question count are questions, and question count + 1 is results. Shown state requires every answer and the result step. Disabled test-out accepts only step 0. | +| `format(template, values)` | Replaces known `{key}` placeholders once. Inserted values stay literal. | + +Without JavaScript, learners can read the outline, answer native questions and open "Answers" for correct options and explanations. There is no computed course plan. + +Set `allowTestOut` to `false` when all sections are required. The outline explains this and hides Start. Learners cannot change the setting. + +Passing a section requires every answer to be correct. A passed section also credits its prerequisites and theirs. When several passes credit one section, the lowest numeric passed id supplies credit. A directly failed section stays required even when another pass would credit it. A direct pass takes precedence over credit. Passed and credited sections both show "Skip"; others show "Take it". + +Submission locks answers. Panels show the question number, total and section title. They slide horizontally over 240 ms, or change instantly under reduced motion. + +Optional `state: { read, write }` stores `{ picks: { [questionId]: optionId }, shown, step }`. Invalid state is ignored. Partial picks restore to the saved panel; shown results restore silently without moving focus. Picks, panel changes, submission and reset write state. If a restored last panel has earlier unanswered questions, Check returns to the first one. + +Repeated enhancement returns the same instance. `destroy()` removes listeners, clears feedback, unlocks radios and restores native Answers. Host state errors propagate. + +## Use it + +Copy `patterns/test-out/` and `lib/`, keeping their relative paths. Link `lib/base.css`, then `patterns/test-out/pattern.css`. Use a unique id prefix per instance. + +```js +import { render } from './patterns/test-out/render.js'; +import { enhance } from './patterns/test-out/enhance.js'; +import { strings } from './patterns/test-out/strings.js'; +import content from './patterns/test-out/examples/en.json' with { type: 'json' }; + +document.querySelector('main').innerHTML = render(content, strings.en, { id: 'placement', lang: 'en' }); +const instance = enhance(document.querySelector('[data-lp-pattern]'), { content, strings: strings.en }); +``` + +## Adapt it with your agent + +> Adapt the test-out examples to [topic] for [audience]. Keep `{ title, allowTestOut, sections: [{ id, title, requires }], questions: [{ id, section, text, options: [{ id, text }], correct, explanation }] }`. Use three or four sections with one or two questions each. Advanced questions must test the prerequisites they credit. Keep ids and references valid, with no prerequisite cycles. Write English and Québec French together, using vous. Preserve native questions and answers, state, local marks, one summary announcement and reflow. Do not claim that one or two answers prove mastery. Ask the author whether skipping is allowed. + +## Licence + +MIT. Keep the [licence notice](../../LICENSE) with copied code. diff --git a/docs/provenance/learning-patterns/patterns/write-distractors/README.md b/docs/provenance/learning-patterns/patterns/write-distractors/README.md new file mode 100644 index 0000000..927edd7 --- /dev/null +++ b/docs/provenance/learning-patterns/patterns/write-distractors/README.md @@ -0,0 +1,131 @@ +--- +title: Write the wrong options +title_fr: Rédigez les mauvaises réponses +summary: Answer from memory, write wrong options and explain the mistaken idea behind each, then compare with the author. +section: question +ai: no +offline: yes +learners: not tried +--- +# Write the wrong options + +Answer from memory, then write wrong options and name the mistaken idea behind each. Compare your options with the author's. + +## Use it when + +Use it when learners know enough to answer from memory and explain common mistakes. Authors can review their wrong options for use in quizzes. + +## Avoid it when + +A person must judge whether each option expresses the chosen misconception and would tempt a learner. Matching labels cannot do that. + +## How it works + +1. Answer from memory. Select "Check my answer" to see the right answer. +2. Choose "Yes" or "Not quite" to say whether your answer was right. This choice has no score. +3. Write the requested wrong options. Choose the mistaken idea behind each, or choose "Something else" and describe it. +4. Select "Compare with the author's". Fix any fields with errors. +5. Compare your question with the author's. Each wrong option names its mistaken idea. A check mark shows a matching label. The summary counts matching and extra labels. +6. Select "Start over" to clear your work. + +## Evidence + +Giving your own answer helps you remember more than reading one. The average effect was 0.40 standard deviations across 86 studies ([Bertsch et al., 2007](https://doi.org/10.3758/BF03193441)). University students wrote questions about a lecture. A week later, they recalled about as much as students who answered practice questions ([Ebersbach, Feierabend and Nazari, 2020](https://doi.org/10.1002/acp.3639)). Students who wrote multiple-choice questions for peers tended to do better in exams. Those studies show a link, not that writing questions caused better scores ([Hardy et al., 2014](https://doi.org/10.1080/09500693.2014.916831)). + +Linking each wrong option to a named misconception follows diagnostic test design ([Treagust, 1988](https://doi.org/10.1080/0950069880100204); [Eedi](https://www.eedi.com/news/from-wrong-answers-to-real-insights-how-we-used-a-kaggle-challenge-to-map-student-misconceptions)). Texts that explain why a misconception is wrong improved learning in a meta-analysis ([Schroeder and Kucera, 2022](https://doi.org/10.1007/s10648-021-09656-z)). + +This pattern shows the right answer but does not explain why each misconception is wrong. Writing wrong options has not been tested as a learning activity. + +## Accessibility + +Follows the shared baseline in the root README. + +- Each wrong option has a fieldset. Its legend names the option's letter and labels its answer field. Error messages name options by that letter. Other fields have visible labels. Errors link to their fields. +- Checking announces the right answer and keeps focus on Check my answer. Choosing Yes or Not quite focuses the next heading. +- An invalid comparison focuses the first error and announces how many fields need fixing. A valid comparison keeps focus on the submit button and announces the matching-label count. +- After comparison, each wrong option shows a summary you can read but cannot edit. It includes the full misconception label. Start over focuses Your answer. + +## Content fields + +Content is plain text. Rendering escapes HTML characters. + +| Field | Meaning | +| --- | --- | +| `question` | Question to answer. | +| `rightAnswer` | Right answer, shown after checking or in the native disclosure. | +| `misconceptions` | At least one named misconception, in select order. | +| `misconceptions[].id` | Unique letters, digits, underscores or hyphens. `other` is reserved for "Something else". | +| `misconceptions[].label` | Label in the select menu and results. | +| `authorOptions` | At least one wrong option for comparison. | +| `authorOptions[].text` | Authored wrong answer. | +| `authorOptions[].misconception` | An ID in `misconceptions`. | +| `count` | Positive safe integer. Number of wrong options to write; keep it small. The example uses two. | + +Strings must be nonempty. Unknown fields, duplicate ids and invalid author references throw errors naming the field. Use `validateContent` for the cross-field checks recorded as `x-uniqueBy` and `x-reference`; ordinary JSON Schema tools need these checks. + +## Logic + +`count` is an integer from one to five, matching the bilingual authored count names. + +Authored text is capped at 120 characters for titles and labels, 400 for questions and prompts, 300 for options, and 1,500 for answers, explanations and passage text. The schema gives each field's limit; `validateContent` enforces it. + +`logic.js` has no DOM code. Hosts can use these functions to build their own interface. + +| Function | Returns | +| --- | --- | +| `validateContent(content)` | Nothing. Throws an `Error` naming the first bad field. | +| `validateAnswer(text)` | `{ ok: true, text }` with trimmed text, or `{ ok: false, error }` with `emptyAnswer` or `longAnswer`. | +| `validateOptions(content, options)` | `{ ok: true, options }` with clean text, or `{ ok: false, errors }`. Each error has `{ option, field, code }`, with a zero-based option index. | +| `targetOf(content, option)` | The named target's label or the learner's custom description. Throws for an unknown target. | +| `coverage(content, options)` | `{ targeted, missed, extra, matches }`. The first three fields hold unique target labels. `matches` holds one boolean per learner option. | +| `coverageMessage(content, options)` | `{ authorTargeted, authorTotal, ownExtra, untargeted }`. Counts unique targets in the author's options and the learner's extra targets. `untargeted` holds missed author labels in author-option order. | +| `validateState(content, value)` | A copy of valid `{ answer, hadIt, options, shown }` state, or `null`. Oversized answers keep their full text and options with `hadIt: null`, `shown: false`. Legacy `{ options, shown }` restores as a draft with `answer: ''`, `hadIt: null`, `shown: false`. | +| `optionKey(index)` | A letter key for a zero-based index: `0` gives A, `1` gives B, `26` gives AA. Throws for an index that is not a nonnegative safe integer. | + +The initial learner answer must be nonblank and at most `MAX_ANSWER` (`2000`) UTF-16 code units after trimming, matching first-answer. Check refuses an oversized answer with a field error and keeps the original text intact. The textarea has no `maxlength`, so pasted or restored text stays readable, selectable and copyable; leading and trailing whitespace does not consume the limit. + +Each submitted option has `{ text, misconception, custom }`. Named targets use an empty `custom` string. Exported constants `MAX_OPTION`, `MAX_CUSTOM` and `OTHER` are `300`, `120` and `'other'`. Option and custom-description length checks count UTF-16 code units before trimming and collapsing whitespace. Comparison rejects the right answer and duplicate options after ignoring case and extra whitespace. + +Coverage compares labels, ignoring case and extra whitespace. Custom labels can match authored labels. Repeated labels count once. `missed` lists only labels from the author's options; `extra` lists learner labels absent from those options. `coverageOne` or `coverageMany` formats counts above two question previews. Previews sit side by side at content widths of at least 40rem and stack below that. + +Checking locks the first answer. The right answer has letter A; wrong options start at B. The heading writes counts two to five in words and larger counts in digits. The author's question appears after a valid comparison. Without JavaScript, learners can read the question, write an answer and open Right answer and The author's question. + +Optional host state stores `{ answer, hadIt, options: [{ text, misconception, custom }], shown }`. `answer` is the learner's text. `hadIt` is `true` for Yes, `false` for Not quite, or `null` before that choice. Input and selection save drafts; comparison saves normalized options; Start over saves empty fields. + +A saved draft with `hadIt: null` returns to the answer step, even if previously checked. An otherwise valid saved state with an oversized answer restores silently as an editable draft: the full answer and wrong options survive, while `hadIt` becomes `null` and `shown` becomes `false`. The learner must shorten and check the answer again before proceeding; no completed Check or comparison is restored. Legacy state keeps the wrong-option draft and requires the answer step again. Invalid state is ignored. Valid results restore silently. State adapter errors reach the host. Repeated enhancement returns the same instance. + +The host decides how to collect submissions. The pattern uses no localStorage and keeps no suggestion list. + +Override `--lp-*` tokens for colours, radius, fonts and focus. Defaults are in the root README. + +## Use it + +Copy this folder and `lib/`, keeping their relative paths. Link `lib/base.css`, then `patterns/write-distractors/pattern.css`. Render on your server with a unique id prefix for each instance. + +```js +import { render } from './patterns/write-distractors/render.js'; +import { strings } from './patterns/write-distractors/strings.js'; +const markup = render(content, strings.en, { id: 'breaks', lang: 'en' }); +``` + +`render.js` also exports `renderQuestionPreview(content, strings, options, { author, matches, heading })`, which returns escaped HTML for a read-only question. `options` holds wrong options; `matches` holds their comparison flags. `author` chooses the author's title, and `heading: false` omits it inside a labelled disclosure. + +After inserting the server markup, enable the fields in the browser. + +```js +import { enhance } from './patterns/write-distractors/enhance.js'; +const instance = enhance(document.querySelector('[data-lp-pattern="write-distractors"]'), { + content, strings: strings.en, + state: { read: () => saved, write: value => { saved = value; } } +}); +// Remove listeners and restore the native details when the host removes the interaction. +instance.destroy(); +``` + +## Adapt it with your agent + +Give your agent this README, the examples and your correct answer. Ask for misconception labels and plausible wrong options to review. Keep feedback authored in `strings.js`. Check labels against real learner errors. If you change the content contract, run logic, schema, type, budget and browser checks. + +## Licence + +[MIT](../../LICENSE). Keep the licence notice with copied code. diff --git a/package.json b/package.json index 1b6bd8f..df95759 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "engines": { "node": ">=24.18.0 <25" }, "scripts": { "test": "node --test", + "check:composer-patterns": "node scripts/check-composer-patterns.mjs", "validate": "node scripts/validate-manifest.mjs pack.json", "validate:release": "node scripts/validate-manifest.mjs pack.json --release", "build:adapters": "node scripts/build-adapters.mjs" diff --git a/scripts/check-composer-patterns.mjs b/scripts/check-composer-patterns.mjs new file mode 100644 index 0000000..0487f03 --- /dev/null +++ b/scripts/check-composer-patterns.mjs @@ -0,0 +1,93 @@ +import { readdir, readFile, writeFile } from 'node:fs/promises'; +import { createHash } from 'node:crypto'; +import { join, resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const START = ''; +const END = ''; +const root = fileURLToPath(new URL('../', import.meta.url)); +const pinnedLibrary = join(root, 'docs/provenance/learning-patterns'); + +function sections(text, name) { + const found = new Map(); + let current; + let fence; + for (const line of text.replaceAll('\r\n', '\n').split('\n')) { + const marker = /^\s{0,3}(`{3,}|~{3,})/.exec(line)?.[1]; + if (marker) { + if (!fence) fence = marker; + else if (marker[0] === fence[0] && marker.length >= fence.length) fence = undefined; + continue; + } + if (fence) continue; + const heading = /^(#{1,2}) (.+?)\s*$/.exec(line); + if (heading) { + current = heading[1] === '##' && ['Use it when', 'Avoid it when'].includes(heading[2]) + ? heading[2] : undefined; + if (current) { + if (found.has(current)) throw new Error(`${name}: duplicate ${current}`); + found.set(current, []); + } + } else if (current) found.get(current).push(line); + } + return ['Use it when', 'Avoid it when'].map(heading => { + const content = found.get(heading)?.join(' ').replace(/\s+/g, ' ').trim(); + if (!content) throw new Error(`${name}: missing or empty ${heading}`); + return content.replaceAll('|', '\\|'); + }); +} + +async function main() { + const args = process.argv.slice(2); + const write = args.at(-1) === '--write'; + if (write) args.pop(); + if ((args.length !== 0 && args.length !== 2) || args.some(arg => arg.startsWith('--'))) { + throw new Error('Usage: node check-composer-patterns.mjs [ ] [--write]'); + } + const [library = pinnedLibrary, target = join(root, 'skills/composer/patterns.md')] = args; + const rows = []; + const entries = await readdir(join(library, 'patterns'), { withFileTypes: true }); + const patterns = entries.filter(entry => entry.isDirectory()).map(entry => entry.name).sort(); + if (resolve(library) === resolve(pinnedLibrary)) { + const pin = JSON.parse(await readFile(join(root, 'docs/provenance/learning-patterns.json'), 'utf8')); + if (!/^[a-f0-9]{40}$/.test(pin.sourceRevision) || pin.source !== `https://github.com/Praxity/learning-patterns/tree/${pin.sourceRevision}`) { + throw new Error('Invalid learning-patterns source revision'); + } + const expected = ['LICENSE', ...patterns.map(name => `patterns/${name}/README.md`)]; + if (JSON.stringify(Object.keys(pin.files).sort()) !== JSON.stringify(expected.sort())) { + throw new Error('Pinned file inventory does not match the library READMEs'); + } + for (const file of expected) { + const bytes = await readFile(join(library, file)); + if (createHash('sha256').update(bytes).digest('hex') !== pin.files[file]) { + throw new Error(`Pinned SHA-256 mismatch: ${file}`); + } + } + } + for (const name of patterns) { + const readme = await readFile(join(library, 'patterns', name, 'README.md'), 'utf8'); + const [use, avoid] = sections(readme, `${name}/README.md`); + rows.push(`| \`${name}\` | ${use} | ${avoid} |`); + } + if (!rows.length) throw new Error('No library patterns found'); + const table = ['| Pattern | Use it when | Avoid it when |', '| --- | --- | --- |', ...rows].join('\n'); + const original = await readFile(target, 'utf8'); + const text = original.replaceAll('\r\n', '\n'); + if (text.split(START).length !== 2 || text.split(END).length !== 2 || text.indexOf(END) < text.indexOf(START)) { + throw new Error('patterns.md needs exactly one ordered pair of library-pattern-guidance markers'); + } + const start = text.indexOf(START) + START.length; + const end = text.indexOf(END); + const expected = `\n\n${table}\n\n`; + if (text.slice(start, end) !== expected) { + if (!write) throw new Error('Library guidance is stale. Run the same command with --write and review the diff.'); + const updated = text.slice(0, start) + expected + text.slice(end); + await writeFile(target, original.includes('\r\n') ? updated.replaceAll('\n', '\r\n') : updated); + } + console.log(`Library guidance matches ${rows.length} pattern READMEs.`); +} + +main().catch(error => { + console.error(error.message); + process.exitCode = 1; +}); diff --git a/skills/composer/patterns.md b/skills/composer/patterns.md index 6cc9c5d..ab74388 100644 --- a/skills/composer/patterns.md +++ b/skills/composer/patterns.md @@ -2,10 +2,35 @@ Pick from this file in step 4 of the composer skill. Each entry gives the pattern id to write in the outline, whether Studio can publish it now, the barriers it answers, when to use it and when to avoid it. -Library patterns quote their README in [Praxity/learning-patterns](https://github.com/Praxity/learning-patterns/tree/main/patterns). That README owns the pattern; when this file and the README disagree, the README wins. Studio's own compositions have no README yet, so their entries live here. The prax-format skill owns how to write each block. +Library guidance below is generated from the READMEs in [Praxity/learning-patterns at 85f03f1](https://github.com/Praxity/learning-patterns/tree/85f03f1652ff756579e82e1a386345ef28ecd894/patterns). Those READMEs own when to use and avoid each library pattern. Studio's own compositions have no README yet, so their entries live here. Studio support, barriers and fallbacks also live here. The prax-format skill owns how to write each block. Output matters. Rules (show, hide, jump) run only in Pages: Guided slides and PDF reject them. Learner PDF also refuses self-checks. +## Library guidance + +Use these sections for every library pattern, including the planned patterns. Check Studio support below before choosing one. + + + +| Pattern | Use it when | Avoid it when | +| --- | --- | --- | +| `conversation` | Use it to practise a difficult workplace conversation. You can acknowledge the impact, defend the change, attack, withdraw or ask for a pause. Michel responds to each choice. The review explains the effects of your two replies. | It gives no score. | +| `course-lookup` | Use it across a course with a small, stable catalogue. The demo has eight FAQ entries and six course sections. Both shapes use the same renderer and enhancer, configured with `kind: 'faq' \| 'sections'`. | For catalogues with more than about 30 entries, first narrow the candidates with embeddings. Check the chosen candidates against the learner's question before showing them. | +| `dont-know` | Use it for short practice quizzes where learners need a way to say they are unsure. Set points for right, wrong and unknown answers. Losing points for wrong answers may discourage guessing. | Test the scoring with your learners before using it for assessment. | +| `explain-back` | Use it after a short reading. This example checks three ideas: stonewalling, announcing a pause and returning to the conversation. The feedback helps you revise your explanation. | It gives no grade. | +| `first-answer` | Use it when learners can answer the same question at the start and end of a course. Keep the question and self-check criteria the same. | Learners judge their own changes, so use another method to grade answers. | +| `highlight` | Use it to find key ideas or evidence for a question. Keep passages short and split them into meaningful pieces. | Learners select whole pieces, so use another pattern for selecting individual words. Searching visible text does not test recall. | +| `journal` | Use it for weekly course reflections. Each suggestion asks about the first missing detail: a situation, your response, a next step or when to try it. | There is no score or checklist. The distress check was tested only on agent-written answers. It shows an authored support message instead of a question. It does not diagnose, contact anyone or send alerts. Replace the message with your real support details. Test the decision model with your own examples before use with learners. | +| `live-feedback` | Use it for short action plans, outline-like writing assignments, and generally to give learners immediate feedback on responses that can be classified. This example checks for three different actions a colleague could see or hear, a time or situation, and your own commitments. The feedback helps you revise. | It gives no grade. | +| `misconception` | Use it when a short answer can reveal a mistaken belief. This example asks whether rereading notes prepares you for a test. The proxy checks four study misconceptions. Feedback helps you revise your reasoning. | It gives no grade. | +| `retrieval-sheet` | Use it for a short module with a few ideas or actions worth recalling. Keep questions focused and answers short. Learners can use the sheet without internet. Print it or save a PDF while connected. A SCORM package prints from its course files without fetching anything. | Choosing a date sends no reminder. Arrange one through your course host if needed. | +| `review-prompts` | Use it for short readings with facts or actions learners need to recall later. Give each section one focused question and a short answer. | Ratings record what learners say they remembered. They do not verify recall or workplace performance. | +| `self-check` | Use it when learners can check their answers against a short list of clear parts. It works offline and can help you compare self-checking with automated feedback. | The pattern does not judge the learner's text. | +| `test-out` | Use it for a small refresher where one or two questions can justify skipping a section. Advanced questions must test any prerequisite knowledge they credit. | Keep all sections required when the course needs proof that learners saw each one. The host course handles navigation and completion records. | +| `write-distractors` | Use it when learners know enough to answer from memory and explain common mistakes. Authors can review their wrong options for use in quizzes. | A person must judge whether each option expresses the chosen misconception and would tempt a learner. Matching labels cannot do that. | + + + ## Quick lookup | Barrier | Usable now | Planned | @@ -40,8 +65,7 @@ Output matters. Rules (show, hide, jump) run only in Pages: Guided slides and PD - Studio: now. The `dontKnow` setting on a single-answer choice; the prax-format skill documents its default. Studio counts it as wrong, with no penalty. Say in the outline where you want it on, and where it would read oddly, such as a reply in a conversation. - If you don't choose `dont-know`, set `dontKnow=false` on practice choices. - Answers: knowledge, and motivation when learners guess to hide what they don't know. -- Use it "for short practice quizzes where learners need a way to say they are unsure" (README). -- Avoid on scored questions until the scoring has been tested with your learners (README). +- Selection guidance: see [Library guidance](#library-guidance). ### knowledge-check - Studio: now. A single-answer `choice` with feedback on each option, placed between short teaching sections. @@ -59,14 +83,12 @@ Output matters. Rules (show, hide, jump) run only in Pages: Guided slides and PD ### retrieval-sheet - Studio: now. `::: retrieval-sheet`, 1 to 8 questions. - Answers: knowledge (forgetting after the course). -- Use it "for a short module with a few ideas or actions worth recalling". Learners can use it without internet (README). -- Avoid counting on it to bring learners back: "choosing a date sends no reminder" (README). +- Selection guidance: see [Library guidance](#library-guidance). ### self-check - Studio: now, not in learner PDF. `::: self-check` with a model answer and criteria. - Answers: skill (producing written work), knowledge (explaining an idea). -- Use it "when learners can check their answers against a short list of clear parts" (README). -- Avoid when you need a grade: "the pattern does not judge the learner's text" (README). +- Selection guidance: see [Library guidance](#library-guidance). ### sort - Studio: now. `categorize`, `match` or `order`. @@ -123,19 +145,21 @@ Output matters. Rules (show, hide, jump) run only in Pages: Guided slides and PD Record these as rejected (planned) when they are the best fit, and use the fallback. -| Pattern | Status | Use when (README) | Fallback now | -| --- | --- | --- | --- | -| `write-distractors` | Studio adoption in progress; cannot publish | Learners know enough to answer from memory and explain common mistakes | none | -| `highlight` | Studio support accepted, not built | Finding key ideas or evidence in a short passage; not recall | `hotspot` over an image of the passage, or a multiple-answer `choice` listing its pieces | -| `review-prompts` | Studio support accepted, not built | Short readings with facts or actions to recall later | `knowledge-check` or `flashcards` between sections, without review dates | -| `first-answer` | Studio adoption deferred | The same question at the start and end; learners judge their own change | `self-check` at the end; the saved first answer is lost | -| `test-out` | Studio adoption deferred | A small refresher where one or two questions justify skipping a section | `check-first` | -| answer with confidence | Library pattern next; Studio later | Learners mark sure, guessed or don't know | `dont-know` | -| `conversation` (AI) | Library only | Practising a difficult workplace conversation | `dialogue-branching` | -| `explain-back` (AI) | Library only | Explaining a short reading in your own words | `self-check` | -| `live-feedback` (AI) | Library only | Short classifiable writing such as action plans | `self-check` | -| `misconception` (AI) | Library only | A short answer that can reveal a mistaken belief | `knowledge-check` with feedback that explains the contrast | -| `journal` (AI) | Library only | Weekly reflection | `free-response {private}` | -| `course-lookup` (AI) | Library only | Questions across a course with a small, stable catalogue | none | +For library patterns, use and avoid guidance is in [Library guidance](#library-guidance). The proposed answer-with-confidence pattern lets learners mark sure, guessed or don't know; it has no library README yet. + +| Pattern | Status | Fallback now | +| --- | --- | --- | +| `write-distractors` | Studio adoption in progress; cannot publish | none | +| `highlight` | Studio support accepted, not built | `hotspot` over an image of the passage, or a multiple-answer `choice` listing its pieces | +| `review-prompts` | Studio support accepted, not built | `knowledge-check` or `flashcards` between sections, without review dates | +| `first-answer` | Studio adoption deferred | `self-check` at the end; the saved first answer is lost | +| `test-out` | Studio adoption deferred | `check-first` | +| answer with confidence | Library pattern next; Studio later | `dont-know` | +| `conversation` (AI) | Library only | `dialogue-branching` | +| `explain-back` (AI) | Library only | `self-check` | +| `live-feedback` (AI) | Library only | `self-check` | +| `misconception` (AI) | Library only | `knowledge-check` with feedback that explains the contrast | +| `journal` (AI) | Library only | `free-response {private}` | +| `course-lookup` (AI) | Library only | none | AI patterns let a model pick among messages the author wrote; the model writes no learner text. They need a server to run the model, which Studio courses don't have. Ideas in the pattern gallery that are not in this file can't be chosen yet. diff --git a/test/composer-patterns.test.mjs b/test/composer-patterns.test.mjs new file mode 100644 index 0000000..14f8543 --- /dev/null +++ b/test/composer-patterns.test.mjs @@ -0,0 +1,162 @@ +import { test } from 'node:test'; +import assert from 'node:assert/strict'; +import { mkdtemp, mkdir, writeFile, readFile, rm, copyFile } from 'node:fs/promises'; +import { createHash } from 'node:crypto'; +import { tmpdir } from 'node:os'; +import { join } from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { spawnSync } from 'node:child_process'; + +const script = fileURLToPath(new URL('../scripts/check-composer-patterns.mjs', import.meta.url)); +const readme = '# Example\n\n## Use it when\n\nRecall a few ideas.\n\n## Avoid it when\n\nYou need a grade.\n\n## How it works\n\nOther text.\n'; +const before = '# Patterns\n\n## Studio compositions\n\nKeep this hand-written guidance.\n\n'; +const after = '\n\n## Studio support\n\nKeep these statuses and fallbacks.\n'; +const block = '\n\n| Pattern | Use it when | Avoid it when |\n| --- | --- | --- |\n| `example` | Recall a few ideas. | You need a grade. |\n\n'; + +async function fixture(t, source = readme, composer = before + block + after) { + const root = await mkdtemp(join(tmpdir(), 'composer-staleness-')); + t.after(() => rm(root, { recursive: true })); + const folder = join(root, 'patterns', 'example'); + await mkdir(folder, { recursive: true }); + const sourcePath = join(folder, 'README.md'); + const target = join(root, 'patterns.md'); + await writeFile(sourcePath, source); + await writeFile(target, composer); + return { root, sourcePath, target, run: (...args) => spawnSync(process.execPath, [script, root, target, ...args], { encoding: 'utf8', timeout: 10_000 }) }; +} + +test('compares both sections, ignores unrelated sections and accepts CRLF', async t => { + const f = await fixture(t, readme.replaceAll('\n', '\r\n'), (before + block + after).replaceAll('\n', '\r\n')); + assert.equal(f.run().status, 0); +}); + +for (const sentence of ['Recall a few ideas.', 'You need a grade.']) { + test(`fails when source changes: ${sentence}`, async t => { + const f = await fixture(t, readme.replace(sentence, 'Changed guidance.')); + const result = f.run(); + assert.equal(result.status, 1); + assert.match(result.stderr, /guidance is stale/); + }); + test(`fails when composer changes: ${sentence}`, async t => { + const f = await fixture(t, readme, (before + block + after).replace(sentence, 'Changed guidance.')); + assert.equal(f.run().status, 1); + }); +} + +test('write refreshes only the generated region, preserves CRLF and then passes', async t => { + const f = await fixture(t, readme.replace('Recall a few ideas.', 'Choose `one | two`.\n\nKeep it short.'), (before + block + after).replaceAll('\n', '\r\n')); + assert.equal(f.run().status, 1); + assert.equal(f.run('--write').status, 0); + assert.equal(await readFile(f.target, 'utf8'), (before + block.replace('Recall a few ideas.', 'Choose `one \\| two`. Keep it short.') + after).replaceAll('\n', '\r\n')); + assert.equal(f.run().status, 0); +}); + +for (const heading of ['Use it when', 'Avoid it when']) { + test(`refuses missing, empty or duplicate ${heading}`, async t => { + for (const source of [readme.replace(`## ${heading}`, '## Other'), readme.replace(/Recall a few ideas\.|You need a grade\./g, ''), readme + `\n## ${heading}\nDuplicate.\n`]) { + const f = await fixture(t, source); + const result = f.run('--write'); + assert.equal(result.status, 1); + assert.match(result.stderr, /missing or empty|duplicate/); + } + }); +} + +test('fails on added and removed patterns and rejects a missing README', async t => { + const f = await fixture(t); + const added = join(f.root, 'patterns', 'second'); + await mkdir(added); + assert.equal(f.run().status, 1); + await writeFile(join(added, 'README.md'), readme); + assert.equal(f.run().status, 1); + assert.equal(f.run('--write').status, 0); + await rm(added, { recursive: true }); + assert.equal(f.run().status, 1); + await rm(join(f.root, 'patterns', 'example'), { recursive: true }); + assert.match(f.run('--write').stderr, /No library patterns/); +}); + +test('refuses absent, duplicate or reversed markers without modifying the file', async t => { + for (const composer of [before, block + block, '\n']) { + const f = await fixture(t, readme, composer); + assert.equal(f.run('--write').status, 1); + assert.equal(await readFile(f.target, 'utf8'), composer); + } +}); + +test('ignores headings in fenced examples and normalizes source wrapping', async t => { + const f = await fixture(t, readme.replace('Recall a few ideas.', 'Recall a few\nideas.\n\n~~~md\n## Avoid it when\nFake guidance.\n~~~')); + assert.equal(f.run().status, 0); +}); + +test('fails with a readable error for missing files and invalid arguments', async t => { + const f = await fixture(t); + assert.match(f.run('--unknown').stderr, /Usage:/); + await rm(f.target); + assert.match(f.run().stderr, /ENOENT/); +}); + +test('checked-in composer guidance matches the pinned library without a live clone', () => { + const result = spawnSync(process.execPath, [script], { encoding: 'utf8', timeout: 10_000 }); + assert.equal(result.status, 0, result.stderr); + assert.match(result.stdout, /matches 14 pattern READMEs/); +}); + +async function pinnedFixture(t) { + const f = await fixture(t); + const localScript = join(f.root, 'scripts/check-composer-patterns.mjs'); + const library = join(f.root, 'docs/provenance/learning-patterns'); + const sourcePath = join(library, 'patterns/example/README.md'); + const target = join(f.root, 'skills/composer/patterns.md'); + await mkdir(join(f.root, 'scripts')); + await mkdir(join(library, 'patterns/example'), { recursive: true }); + await mkdir(join(f.root, 'skills/composer'), { recursive: true }); + await copyFile(script, localScript); + await writeFile(sourcePath, readme); + await writeFile(join(library, 'LICENSE'), 'MIT fixture\n'); + await writeFile(target, before + block + after); + const revision = '0123456789012345678901234567890123456789'; + const pin = { + source: `https://github.com/Praxity/learning-patterns/tree/${revision}`, + sourceRevision: revision, + files: { + LICENSE: createHash('sha256').update('MIT fixture\n').digest('hex'), + 'patterns/example/README.md': createHash('sha256').update(readme).digest('hex'), + }, + }; + const pinPath = join(f.root, 'docs/provenance/learning-patterns.json'); + await writeFile(pinPath, JSON.stringify(pin)); + return { library, sourcePath, target, pinPath, pin, run: (...args) => spawnSync(process.execPath, [localScript, ...args], { cwd: tmpdir(), encoding: 'utf8', timeout: 10_000 }) }; +} + +test('default CLI resolves the pinned sources from the script and rejects tampering before writing', async t => { + const f = await pinnedFixture(t); + assert.equal(f.run().status, 0); + const original = await readFile(f.target, 'utf8'); + await writeFile(f.sourcePath, readme.replace('You need a grade.', 'Changed guidance.')); + assert.equal(f.run().status, 1); + assert.match(f.run('--write').stderr, /Pinned SHA-256 mismatch: patterns\/example\/README.md/); + assert.equal(await readFile(f.target, 'utf8'), original); +}); + +test('a deliberate pin update still fails until the composer guidance is regenerated', async t => { + const f = await pinnedFixture(t); + const changed = readme.replace('You need a grade.', 'You need an assessed result.'); + await writeFile(f.sourcePath, changed); + f.pin.files['patterns/example/README.md'] = createHash('sha256').update(changed).digest('hex'); + await writeFile(f.pinPath, JSON.stringify(f.pin)); + assert.match(f.run().stderr, /guidance is stale/); + assert.equal(f.run('--write').status, 0); + assert.equal(f.run().status, 0); + assert.match(await readFile(f.target, 'utf8'), /You need an assessed result\./); +}); + +test('default CLI rejects an invalid revision, a missing pin, or an incomplete file inventory', async t => { + const f = await pinnedFixture(t); + await writeFile(f.pinPath, JSON.stringify({ ...f.pin, sourceRevision: 'main' })); + assert.match(f.run().stderr, /Invalid learning-patterns source revision/); + await writeFile(f.pinPath, JSON.stringify({ ...f.pin, files: {} })); + assert.match(f.run().stderr, /Pinned file inventory/); + await rm(f.pinPath); + assert.match(f.run().stderr, /ENOENT/); +}); diff --git a/test/golden/adapters.json b/test/golden/adapters.json index a4285f3..66b617f 100644 --- a/test/golden/adapters.json +++ b/test/golden/adapters.json @@ -2,7 +2,7 @@ "claude": { ".claude/skills/assessment-writing/SKILL.md": "8b060ec067294cb14672dbf13347500cc7127334112c8ab179148ae6ea57b2b9", ".claude/skills/composer/outline-template.md": "afea4a0ce1f8a924fb9fc57e8f7dc00ec4a513acdc4cb0acad02a94ca945da7e", - ".claude/skills/composer/patterns.md": "f4d8e926d9ede4d3c0df7629a65e93d92ad61e94f2f1b3ce55587bd1e05b27e8", + ".claude/skills/composer/patterns.md": "22d982b90f9e0ea050b924897f06730dab701b9497e088e11c32e05c006f7689", ".claude/skills/composer/SKILL.md": "e53e54998bbe4f106e50ac391ba7d57b5ce3d2a3d9c41554bb5487c1ffa84b5d", ".claude/skills/install-praxity/SKILL.md": "85b15d9e716afd95ec959c4f5946248d035805e760bb585f1181e8f20548569c", ".claude/skills/studio-editor/references/annotation-examples.md": "f2fb3bdcea363192e69a29baf901d7865098575d7b8ec2acbe5d00adbc1982ef", @@ -12,7 +12,7 @@ "codex": { ".agents/skills/assessment-writing/SKILL.md": "8b060ec067294cb14672dbf13347500cc7127334112c8ab179148ae6ea57b2b9", ".agents/skills/composer/outline-template.md": "afea4a0ce1f8a924fb9fc57e8f7dc00ec4a513acdc4cb0acad02a94ca945da7e", - ".agents/skills/composer/patterns.md": "f4d8e926d9ede4d3c0df7629a65e93d92ad61e94f2f1b3ce55587bd1e05b27e8", + ".agents/skills/composer/patterns.md": "22d982b90f9e0ea050b924897f06730dab701b9497e088e11c32e05c006f7689", ".agents/skills/composer/SKILL.md": "e53e54998bbe4f106e50ac391ba7d57b5ce3d2a3d9c41554bb5487c1ffa84b5d", ".agents/skills/install-praxity/SKILL.md": "85b15d9e716afd95ec959c4f5946248d035805e760bb585f1181e8f20548569c", ".agents/skills/studio-editor/references/annotation-examples.md": "f2fb3bdcea363192e69a29baf901d7865098575d7b8ec2acbe5d00adbc1982ef", From abb7ccf2b1de63f2e64b27427218877d1686ac21 Mon Sep 17 00:00:00 2001 From: Ariel Harlap <3870784+aharlap@users.noreply.github.com> Date: Mon, 19 Oct 2026 21:12:03 -0400 Subject: [PATCH 2/3] Repin composer guidance after final README review fixes Co-Authored-By: GPT-6.1 Sol --- docs/provenance/learning-patterns.json | 12 +++++------ .../patterns/course-lookup/README.md | 6 +++++- .../patterns/dont-know/README.md | 6 +++++- .../patterns/journal/README.md | 2 ++ .../patterns/test-out/README.md | 6 +++++- scripts/check-composer-patterns.mjs | 5 +++-- skills/composer/patterns.md | 10 +++++----- test/composer-patterns.test.mjs | 20 ++++++++++++++++--- test/golden/adapters.json | 4 ++-- 9 files changed, 50 insertions(+), 21 deletions(-) diff --git a/docs/provenance/learning-patterns.json b/docs/provenance/learning-patterns.json index c189ac1..2db8346 100644 --- a/docs/provenance/learning-patterns.json +++ b/docs/provenance/learning-patterns.json @@ -1,22 +1,22 @@ { - "source": "https://github.com/Praxity/learning-patterns/tree/85f03f1652ff756579e82e1a386345ef28ecd894", - "sourceRevision": "85f03f1652ff756579e82e1a386345ef28ecd894", + "source": "https://github.com/Praxity/learning-patterns/tree/3ed3d0e2ccb373cc3646dd89a6dd33030fab58e6", + "sourceRevision": "3ed3d0e2ccb373cc3646dd89a6dd33030fab58e6", "license": "MIT", "files": { "LICENSE": "d817c04f10efaffd1ab6dba0792a6567dce98f8ab41e86c64833a7b9cff7ecc5", "patterns/conversation/README.md": "e7905b5f6428f051bd713925ab62d730c4439eb9f83156a7129081945109ed21", - "patterns/course-lookup/README.md": "8d998c9f4acf83e4d2e46c31e36737f4c06b3e4fda9be9bd1919aa665a94b8a7", - "patterns/dont-know/README.md": "7426f50a790f4d493520ffd08368592e76217b8de8ae091246a54ab68c743b5e", + "patterns/course-lookup/README.md": "fb51269d7d2cab7d04cc6f7405a14bcbaeac7bd48435c733c7ca243cbda0fdea", + "patterns/dont-know/README.md": "fb877c079d1dbda91245a793213b0bd34e53862c649d9458010cf7d3c3780b00", "patterns/explain-back/README.md": "2de4bf358b88d386a07e6d9dd2ca79e11757e331e2eb6a6fa192b0c1a0b16f0f", "patterns/first-answer/README.md": "f17127befa84657cee5b1a853aefbb5bd962c2c1a0063f14e7daec1e361f0fa9", "patterns/highlight/README.md": "4f7d7615e079b92196b5cbb0c0f2c296683490d60db6e64fefaaa75a793ed91d", - "patterns/journal/README.md": "ec0c57adfd400063640f4bb1b324ff3eaf8d24052313a765b97afab1569dbadf", + "patterns/journal/README.md": "88942cf96636ced39926142462a364cf2c2292724e3c14caa01cf12a57cec4e8", "patterns/live-feedback/README.md": "9dfbe57b30a9525e6ce10912952d51cc45d3896686822d8caa291a23940c9944", "patterns/misconception/README.md": "26e45cdc9bbc70582571ebb3ab391db945848b9b9544a6e9f85a7aafebff2d17", "patterns/retrieval-sheet/README.md": "30b017eb6121534538cdedf934d943b2e57cb4f058113367aadab03e8718aee8", "patterns/review-prompts/README.md": "f53cbe5f287d64010f7a71209ca0172218c1cd009f2a85b34bd0370fcbe0de70", "patterns/self-check/README.md": "b287cbc682d6aeb2d6788574cb9454e6ebd7f9f00247931a2d07d7a17f4a9614", - "patterns/test-out/README.md": "86962152f36d93f0ce765b999d57523b790bb8968953d2cd983c2c0f49eacc85", + "patterns/test-out/README.md": "e5043437b7f91b2b56dc6e14f7d7a9ab8c9e9ef80b122420d8646aa88695cfb8", "patterns/write-distractors/README.md": "91ccba1707d1a2b09ac3a78c55a57262411f6a8d6441c2d08276f039313488e2" } } diff --git a/docs/provenance/learning-patterns/patterns/course-lookup/README.md b/docs/provenance/learning-patterns/patterns/course-lookup/README.md index d853a2c..5c5a38e 100644 --- a/docs/provenance/learning-patterns/patterns/course-lookup/README.md +++ b/docs/provenance/learning-patterns/patterns/course-lookup/README.md @@ -13,10 +13,14 @@ Ask about Assertive at work. The decision model picks an authored answer or a se ## Use it when -Use it across a course with a small, stable catalogue. The demo has eight FAQ entries and six course sections. Both shapes use the same renderer and enhancer, configured with `kind: 'faq' | 'sections'`. +Use it across a course with a small, stable catalogue. The demo has eight FAQ entries and six course sections. ## Avoid it when +## Setting it up + +Both shapes use the same renderer and enhancer, configured with `kind: 'faq' | 'sections'`. + For catalogues with more than about 30 entries, first narrow the candidates with embeddings. Check the chosen candidates against the learner's question before showing them. ## How it works diff --git a/docs/provenance/learning-patterns/patterns/dont-know/README.md b/docs/provenance/learning-patterns/patterns/dont-know/README.md index dfbf2c8..34f5826 100644 --- a/docs/provenance/learning-patterns/patterns/dont-know/README.md +++ b/docs/provenance/learning-patterns/patterns/dont-know/README.md @@ -13,12 +13,16 @@ Answer a quiz with an "I don't know" option. See your score, explanations and qu ## Use it when -Use it for short practice quizzes where learners need a way to say they are unsure. Set points for right, wrong and unknown answers. Losing points for wrong answers may discourage guessing. +Use it for short practice quizzes where learners need a way to say they are unsure. ## Avoid it when Test the scoring with your learners before using it for assessment. +## Setting it up + +Set points for right, wrong and unknown answers. Losing points for wrong answers may discourage guessing. + ## How it works 1. Read the scoring rule and choose an answer for each question. Choose "I don't know" when you are unsure. diff --git a/docs/provenance/learning-patterns/patterns/journal/README.md b/docs/provenance/learning-patterns/patterns/journal/README.md index 8dd5462..5b84b7b 100644 --- a/docs/provenance/learning-patterns/patterns/journal/README.md +++ b/docs/provenance/learning-patterns/patterns/journal/README.md @@ -19,6 +19,8 @@ Use it for weekly course reflections. Each suggestion asks about the first missi There is no score or checklist. +## Setting it up + The distress check was tested only on agent-written answers. It shows an authored support message instead of a question. It does not diagnose, contact anyone or send alerts. Replace the message with your real support details. Test the decision model with your own examples before use with learners. ## How it works diff --git a/docs/provenance/learning-patterns/patterns/test-out/README.md b/docs/provenance/learning-patterns/patterns/test-out/README.md index cd50072..2cc519d 100644 --- a/docs/provenance/learning-patterns/patterns/test-out/README.md +++ b/docs/provenance/learning-patterns/patterns/test-out/README.md @@ -17,7 +17,11 @@ Use it for a small refresher where one or two questions can justify skipping a s ## Avoid it when -Keep all sections required when the course needs proof that learners saw each one. The host course handles navigation and completion records. +Keep all sections required when the course needs proof that learners saw each one. + +## Setting it up + +The host course handles navigation and completion records. ## How it works diff --git a/scripts/check-composer-patterns.mjs b/scripts/check-composer-patterns.mjs index 0487f03..ef6e626 100644 --- a/scripts/check-composer-patterns.mjs +++ b/scripts/check-composer-patterns.mjs @@ -31,8 +31,9 @@ function sections(text, name) { } else if (current) found.get(current).push(line); } return ['Use it when', 'Avoid it when'].map(heading => { - const content = found.get(heading)?.join(' ').replace(/\s+/g, ' ').trim(); - if (!content) throw new Error(`${name}: missing or empty ${heading}`); + if (!found.has(heading)) throw new Error(`${name}: missing ${heading}`); + const content = found.get(heading).join(' ').replace(/\s+/g, ' ').trim(); + if (!content && heading === 'Use it when') throw new Error(`${name}: empty ${heading}`); return content.replaceAll('|', '\\|'); }); } diff --git a/skills/composer/patterns.md b/skills/composer/patterns.md index ab74388..adc6b0a 100644 --- a/skills/composer/patterns.md +++ b/skills/composer/patterns.md @@ -2,7 +2,7 @@ Pick from this file in step 4 of the composer skill. Each entry gives the pattern id to write in the outline, whether Studio can publish it now, the barriers it answers, when to use it and when to avoid it. -Library guidance below is generated from the READMEs in [Praxity/learning-patterns at 85f03f1](https://github.com/Praxity/learning-patterns/tree/85f03f1652ff756579e82e1a386345ef28ecd894/patterns). Those READMEs own when to use and avoid each library pattern. Studio's own compositions have no README yet, so their entries live here. Studio support, barriers and fallbacks also live here. The prax-format skill owns how to write each block. +Library guidance below is generated from the READMEs in [Praxity/learning-patterns at 3ed3d0e](https://github.com/Praxity/learning-patterns/tree/3ed3d0e2ccb373cc3646dd89a6dd33030fab58e6/patterns). Those READMEs own when to use and avoid each library pattern. Studio's own compositions have no README yet, so their entries live here. Studio support, barriers and fallbacks also live here. The prax-format skill owns how to write each block. Output matters. Rules (show, hide, jump) run only in Pages: Guided slides and PDF reject them. Learner PDF also refuses self-checks. @@ -15,18 +15,18 @@ Use these sections for every library pattern, including the planned patterns. Ch | Pattern | Use it when | Avoid it when | | --- | --- | --- | | `conversation` | Use it to practise a difficult workplace conversation. You can acknowledge the impact, defend the change, attack, withdraw or ask for a pause. Michel responds to each choice. The review explains the effects of your two replies. | It gives no score. | -| `course-lookup` | Use it across a course with a small, stable catalogue. The demo has eight FAQ entries and six course sections. Both shapes use the same renderer and enhancer, configured with `kind: 'faq' \| 'sections'`. | For catalogues with more than about 30 entries, first narrow the candidates with embeddings. Check the chosen candidates against the learner's question before showing them. | -| `dont-know` | Use it for short practice quizzes where learners need a way to say they are unsure. Set points for right, wrong and unknown answers. Losing points for wrong answers may discourage guessing. | Test the scoring with your learners before using it for assessment. | +| `course-lookup` | Use it across a course with a small, stable catalogue. The demo has eight FAQ entries and six course sections. | | +| `dont-know` | Use it for short practice quizzes where learners need a way to say they are unsure. | Test the scoring with your learners before using it for assessment. | | `explain-back` | Use it after a short reading. This example checks three ideas: stonewalling, announcing a pause and returning to the conversation. The feedback helps you revise your explanation. | It gives no grade. | | `first-answer` | Use it when learners can answer the same question at the start and end of a course. Keep the question and self-check criteria the same. | Learners judge their own changes, so use another method to grade answers. | | `highlight` | Use it to find key ideas or evidence for a question. Keep passages short and split them into meaningful pieces. | Learners select whole pieces, so use another pattern for selecting individual words. Searching visible text does not test recall. | -| `journal` | Use it for weekly course reflections. Each suggestion asks about the first missing detail: a situation, your response, a next step or when to try it. | There is no score or checklist. The distress check was tested only on agent-written answers. It shows an authored support message instead of a question. It does not diagnose, contact anyone or send alerts. Replace the message with your real support details. Test the decision model with your own examples before use with learners. | +| `journal` | Use it for weekly course reflections. Each suggestion asks about the first missing detail: a situation, your response, a next step or when to try it. | There is no score or checklist. | | `live-feedback` | Use it for short action plans, outline-like writing assignments, and generally to give learners immediate feedback on responses that can be classified. This example checks for three different actions a colleague could see or hear, a time or situation, and your own commitments. The feedback helps you revise. | It gives no grade. | | `misconception` | Use it when a short answer can reveal a mistaken belief. This example asks whether rereading notes prepares you for a test. The proxy checks four study misconceptions. Feedback helps you revise your reasoning. | It gives no grade. | | `retrieval-sheet` | Use it for a short module with a few ideas or actions worth recalling. Keep questions focused and answers short. Learners can use the sheet without internet. Print it or save a PDF while connected. A SCORM package prints from its course files without fetching anything. | Choosing a date sends no reminder. Arrange one through your course host if needed. | | `review-prompts` | Use it for short readings with facts or actions learners need to recall later. Give each section one focused question and a short answer. | Ratings record what learners say they remembered. They do not verify recall or workplace performance. | | `self-check` | Use it when learners can check their answers against a short list of clear parts. It works offline and can help you compare self-checking with automated feedback. | The pattern does not judge the learner's text. | -| `test-out` | Use it for a small refresher where one or two questions can justify skipping a section. Advanced questions must test any prerequisite knowledge they credit. | Keep all sections required when the course needs proof that learners saw each one. The host course handles navigation and completion records. | +| `test-out` | Use it for a small refresher where one or two questions can justify skipping a section. Advanced questions must test any prerequisite knowledge they credit. | Keep all sections required when the course needs proof that learners saw each one. | | `write-distractors` | Use it when learners know enough to answer from memory and explain common mistakes. Authors can review their wrong options for use in quizzes. | A person must judge whether each option expresses the chosen misconception and would tempt a learner. Matching labels cannot do that. | diff --git a/test/composer-patterns.test.mjs b/test/composer-patterns.test.mjs index 14f8543..92b1383 100644 --- a/test/composer-patterns.test.mjs +++ b/test/composer-patterns.test.mjs @@ -52,16 +52,30 @@ test('write refreshes only the generated region, preserves CRLF and then passes' }); for (const heading of ['Use it when', 'Avoid it when']) { - test(`refuses missing, empty or duplicate ${heading}`, async t => { - for (const source of [readme.replace(`## ${heading}`, '## Other'), readme.replace(/Recall a few ideas\.|You need a grade\./g, ''), readme + `\n## ${heading}\nDuplicate.\n`]) { + test(`refuses missing or duplicate ${heading}`, async t => { + for (const source of [readme.replace(`## ${heading}`, '## Other'), readme + `\n## ${heading}\nDuplicate.\n`]) { const f = await fixture(t, source); const result = f.run('--write'); assert.equal(result.status, 1); - assert.match(result.stderr, /missing or empty|duplicate/); + assert.match(result.stderr, /missing|duplicate/); } }); } +test('copies an empty avoid section without inventing guidance and excludes setup notes', async t => { + const source = readme.replace('You need a grade.', '').replace('## How it works', '## Setting it up\n\nConfigure the host.\n\n## How it works'); + const f = await fixture(t, source); + assert.match(f.run().stderr, /guidance is stale/); + assert.equal(f.run('--write').status, 0); + assert.equal(f.run().status, 0); + assert.equal(await readFile(f.target, 'utf8'), before + block.replace('You need a grade.', '') + after); +}); + +test('still rejects an empty use section', async t => { + const f = await fixture(t, readme.replace('Recall a few ideas.', '')); + assert.match(f.run('--write').stderr, /empty Use it when/); +}); + test('fails on added and removed patterns and rejects a missing README', async t => { const f = await fixture(t); const added = join(f.root, 'patterns', 'second'); diff --git a/test/golden/adapters.json b/test/golden/adapters.json index 66b617f..cee9c70 100644 --- a/test/golden/adapters.json +++ b/test/golden/adapters.json @@ -2,7 +2,7 @@ "claude": { ".claude/skills/assessment-writing/SKILL.md": "8b060ec067294cb14672dbf13347500cc7127334112c8ab179148ae6ea57b2b9", ".claude/skills/composer/outline-template.md": "afea4a0ce1f8a924fb9fc57e8f7dc00ec4a513acdc4cb0acad02a94ca945da7e", - ".claude/skills/composer/patterns.md": "22d982b90f9e0ea050b924897f06730dab701b9497e088e11c32e05c006f7689", + ".claude/skills/composer/patterns.md": "6c21df885ad4ea43a0e3fc8d81c288126e0bb8ef25465389775a666dc830f01c", ".claude/skills/composer/SKILL.md": "e53e54998bbe4f106e50ac391ba7d57b5ce3d2a3d9c41554bb5487c1ffa84b5d", ".claude/skills/install-praxity/SKILL.md": "85b15d9e716afd95ec959c4f5946248d035805e760bb585f1181e8f20548569c", ".claude/skills/studio-editor/references/annotation-examples.md": "f2fb3bdcea363192e69a29baf901d7865098575d7b8ec2acbe5d00adbc1982ef", @@ -12,7 +12,7 @@ "codex": { ".agents/skills/assessment-writing/SKILL.md": "8b060ec067294cb14672dbf13347500cc7127334112c8ab179148ae6ea57b2b9", ".agents/skills/composer/outline-template.md": "afea4a0ce1f8a924fb9fc57e8f7dc00ec4a513acdc4cb0acad02a94ca945da7e", - ".agents/skills/composer/patterns.md": "22d982b90f9e0ea050b924897f06730dab701b9497e088e11c32e05c006f7689", + ".agents/skills/composer/patterns.md": "6c21df885ad4ea43a0e3fc8d81c288126e0bb8ef25465389775a666dc830f01c", ".agents/skills/composer/SKILL.md": "e53e54998bbe4f106e50ac391ba7d57b5ce3d2a3d9c41554bb5487c1ffa84b5d", ".agents/skills/install-praxity/SKILL.md": "85b15d9e716afd95ec959c4f5946248d035805e760bb585f1181e8f20548569c", ".agents/skills/studio-editor/references/annotation-examples.md": "f2fb3bdcea363192e69a29baf901d7865098575d7b8ec2acbe5d00adbc1982ef", From 37cebd8417218299dc326c359ebc559111ba1321 Mon Sep 17 00:00:00 2001 From: Ariel Harlap <3870784+aharlap@users.noreply.github.com> Date: Mon, 19 Oct 2026 21:35:42 -0400 Subject: [PATCH 3/3] Pin composer guidance to merged learning-patterns revision Co-Authored-By: GPT-6.1 Sol --- docs/provenance/learning-patterns.json | 4 ++-- skills/composer/patterns.md | 2 +- test/golden/adapters.json | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/provenance/learning-patterns.json b/docs/provenance/learning-patterns.json index 2db8346..fce9a06 100644 --- a/docs/provenance/learning-patterns.json +++ b/docs/provenance/learning-patterns.json @@ -1,6 +1,6 @@ { - "source": "https://github.com/Praxity/learning-patterns/tree/3ed3d0e2ccb373cc3646dd89a6dd33030fab58e6", - "sourceRevision": "3ed3d0e2ccb373cc3646dd89a6dd33030fab58e6", + "source": "https://github.com/Praxity/learning-patterns/tree/e9d4fb6989135a44d75f0765c61d3a596a5d4428", + "sourceRevision": "e9d4fb6989135a44d75f0765c61d3a596a5d4428", "license": "MIT", "files": { "LICENSE": "d817c04f10efaffd1ab6dba0792a6567dce98f8ab41e86c64833a7b9cff7ecc5", diff --git a/skills/composer/patterns.md b/skills/composer/patterns.md index adc6b0a..4326e9d 100644 --- a/skills/composer/patterns.md +++ b/skills/composer/patterns.md @@ -2,7 +2,7 @@ Pick from this file in step 4 of the composer skill. Each entry gives the pattern id to write in the outline, whether Studio can publish it now, the barriers it answers, when to use it and when to avoid it. -Library guidance below is generated from the READMEs in [Praxity/learning-patterns at 3ed3d0e](https://github.com/Praxity/learning-patterns/tree/3ed3d0e2ccb373cc3646dd89a6dd33030fab58e6/patterns). Those READMEs own when to use and avoid each library pattern. Studio's own compositions have no README yet, so their entries live here. Studio support, barriers and fallbacks also live here. The prax-format skill owns how to write each block. +Library guidance below is generated from the READMEs in [Praxity/learning-patterns at e9d4fb6](https://github.com/Praxity/learning-patterns/tree/e9d4fb6989135a44d75f0765c61d3a596a5d4428/patterns). Those READMEs own when to use and avoid each library pattern. Studio's own compositions have no README yet, so their entries live here. Studio support, barriers and fallbacks also live here. The prax-format skill owns how to write each block. Output matters. Rules (show, hide, jump) run only in Pages: Guided slides and PDF reject them. Learner PDF also refuses self-checks. diff --git a/test/golden/adapters.json b/test/golden/adapters.json index cee9c70..116c0af 100644 --- a/test/golden/adapters.json +++ b/test/golden/adapters.json @@ -2,7 +2,7 @@ "claude": { ".claude/skills/assessment-writing/SKILL.md": "8b060ec067294cb14672dbf13347500cc7127334112c8ab179148ae6ea57b2b9", ".claude/skills/composer/outline-template.md": "afea4a0ce1f8a924fb9fc57e8f7dc00ec4a513acdc4cb0acad02a94ca945da7e", - ".claude/skills/composer/patterns.md": "6c21df885ad4ea43a0e3fc8d81c288126e0bb8ef25465389775a666dc830f01c", + ".claude/skills/composer/patterns.md": "ddafb7f99defd2744073079fc29d32f4614cb4c22082e6de4fbb71bbd87fa67d", ".claude/skills/composer/SKILL.md": "e53e54998bbe4f106e50ac391ba7d57b5ce3d2a3d9c41554bb5487c1ffa84b5d", ".claude/skills/install-praxity/SKILL.md": "85b15d9e716afd95ec959c4f5946248d035805e760bb585f1181e8f20548569c", ".claude/skills/studio-editor/references/annotation-examples.md": "f2fb3bdcea363192e69a29baf901d7865098575d7b8ec2acbe5d00adbc1982ef", @@ -12,7 +12,7 @@ "codex": { ".agents/skills/assessment-writing/SKILL.md": "8b060ec067294cb14672dbf13347500cc7127334112c8ab179148ae6ea57b2b9", ".agents/skills/composer/outline-template.md": "afea4a0ce1f8a924fb9fc57e8f7dc00ec4a513acdc4cb0acad02a94ca945da7e", - ".agents/skills/composer/patterns.md": "6c21df885ad4ea43a0e3fc8d81c288126e0bb8ef25465389775a666dc830f01c", + ".agents/skills/composer/patterns.md": "ddafb7f99defd2744073079fc29d32f4614cb4c22082e6de4fbb71bbd87fa67d", ".agents/skills/composer/SKILL.md": "e53e54998bbe4f106e50ac391ba7d57b5ce3d2a3d9c41554bb5487c1ffa84b5d", ".agents/skills/install-praxity/SKILL.md": "85b15d9e716afd95ec959c4f5946248d035805e760bb585f1181e8f20548569c", ".agents/skills/studio-editor/references/annotation-examples.md": "f2fb3bdcea363192e69a29baf901d7865098575d7b8ec2acbe5d00adbc1982ef",