Skip to content

Commit 6c361d1

Browse files
committed
chore: add changeset-driven publish workflow and mc updates
Add reusable scripts/docs for targeted changeset-based publishing while carrying forward the latest mc-populated-blank implementation updates in this branch. Made-with: Cursor
1 parent d5f71a4 commit 6c361d1

10 files changed

Lines changed: 754 additions & 473 deletions

File tree

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ jobs:
223223
id: changesets
224224
uses: changesets/action@v1
225225
with:
226-
publish: bun run release
226+
publish: bun run release:publish
227227
version: bun run version
228228
title: 'chore(release): version packages [skip-heavy-ci]'
229229
commit: 'chore(release): version packages'

README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,29 @@ If a publish fails after the version PR was already merged, rerun the Release wo
117117

118118
This is intended for recovery/rerun scenarios only.
119119

120+
### Targeted Package Release (Changesets)
121+
122+
To release one or more specific packages, create a changeset scoped to those packages:
123+
124+
```bash
125+
bun run changeset:plan -- --packages @pie-element/mc-populated-blank --type patch --summary "Fix CQT parity for VIC/SEL VIC variants"
126+
```
127+
128+
For multiple packages:
129+
130+
```bash
131+
bun run changeset:plan -- --packages @pie-element/mc-populated-blank,@pie-element/simple-cloze --type minor --summary "Add Svelte release updates"
132+
```
133+
134+
Then run the normal Changesets flow:
135+
136+
```bash
137+
# 1) Commit and merge PR with the generated .changeset file
138+
# 2) Let CI create/merge the version PR
139+
# 3) CI publishes via the same command used manually:
140+
bun run release:publish
141+
```
142+
120143
### Maintainer Commands
121144

122145
`bun cli upstream:sync` - (Maintainers only) Syncs packages from the upstream pie-elements project. Requires pie-elements and pie-lib checked out as sibling directories. Analyzes the current state of those projects and copies over what is ready for ESM packaging, including rewrites and restructuring to fit the new project layout.
@@ -126,21 +149,27 @@ This is intended for recovery/rerun scenarios only.
126149
PIE elements include print views for generating paper-based assessments and answer keys. Two complementary players serve different use cases:
127150

128151
### Element-Level Print Player (This Project)
152+
129153
For development and testing of individual elements:
154+
130155
```html
131156
<pie-element-player view="print" element-name="multiple-choice" role="student"></pie-element-player>
132157
```
158+
133159
- **Package:** `@pie-element/element-player`
134160
- **Use for:** Element development, testing, documentation, and optional composable embedding
135161
- **Location:** `packages/element-player/src/players/PieElementPlayer.svelte`
136162

137163
For most production app flows, prefer the standard upstream player stacks in `../pie-elements` and `../pie-players`.
138164

139165
### Item-Level Print Player (pie-players)
166+
140167
For production rendering of complete assessment items:
168+
141169
```html
142170
<pie-print config={{ item: {...}, options: { mode: 'student' } }}></pie-print>
143171
```
172+
144173
- **Package:** `@pie-player/print` (in pie-players repository)
145174
- **Use for:** Production apps, multi-element items, markup-driven rendering
146175
- **Location:** `../pie-players/packages/print-player`

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,11 @@
3939
"lint:fix": "biome check --write .",
4040
"format": "biome format --write .",
4141
"changeset": "changeset",
42+
"changeset:plan": "node ./scripts/create-package-changeset.mjs",
4243
"version": "changeset version",
43-
"release": "bun run build && bun run cli verify:controllers && node ./scripts/changeset-publish-resolved-workspaces.mjs",
44+
"release": "bun run release:publish",
45+
"release:publish": "bun run build && bun run cli verify:controllers && node ./scripts/changeset-publish-resolved-workspaces.mjs",
46+
"release:manual": "bun run release:publish",
4447
"release:label": "node ./scripts/create-release-label.mjs",
4548
"release:label:push": "node ./scripts/create-release-label.mjs --push",
4649
"clean": "turbo run clean && rm -rf node_modules",

packages/elements-svelte/mc-populated-blank/CONTRACT-CHECKLIST.md

Lines changed: 0 additions & 36 deletions
This file was deleted.

packages/elements-svelte/mc-populated-blank/README.md

Lines changed: 110 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,128 @@
11
# @pie-element/mc-populated-blank
22

3-
Svelte 5 PIE element: the learner picks a **multiple-choice** option and that choice **fills a blank** inside an HTML **template**.
3+
Svelte 5 PIE element where the learner picks a **multiple-choice** option and that choice **fills a blank** in a sentence/template.
4+
5+
This element is the PIE counterpart for Star Learnosity CQT "populated blank" interactions. The CQT family had several published `custom_type` flavors, but they are all variants of the same base interaction:
6+
7+
- one selected choice
8+
- one correctness key
9+
- selected choice rendered into a blank target
10+
- optional audio/transcript support
11+
12+
## What the original CQT flavors mean
13+
14+
The historical Learnosity CQT names mostly describe layout/stimulus differences, not different scoring logic:
15+
16+
- `sel_vic`: sentence-style vocabulary in context, with audio/transcript support
17+
- `sr-vic`: sentence-style vocabulary in context, typically no audio button
18+
- `sel_r1-g_plusggg`: lead token before blank (`before_cloze_1`) plus choice set
19+
- `sel_r1-_gplusggg`: blank with a shorter/token-focused stem arrangement
20+
- `sel_r1-_plusggg`: audio + choices only (no blank; modeled as audio-only mode)
21+
- `sel_r1-gg_plusggg`: token-sequence variant with larger glyph-like tokens
22+
- `sel_r1-_ggplusggg`: blank appears before trailing tokens (`after_cloze_*`)
23+
- `sel_r1-s3_plusggg`: stimulus-heavy variant (often image/sentence block + blank + choices)
24+
25+
The shared behavior across these flavors is now represented by one element with configuration, instead of one element per flavor.
26+
27+
## Flavor to model mapping (quick defaults)
28+
29+
Use this as a practical starting point when mapping Learnosity CQT payloads into `mc-populated-blank` models.
30+
31+
| Learnosity `custom_type` | Typical layout meaning | Suggested `interactionMode` | Suggested `choiceMode` | Template hint |
32+
| --- | --- | --- | --- | --- |
33+
| `sel_vic` | sentence cloze with audio/transcript | `populate_blank` | `text` | `<p>{before} {{blank}} {after}</p>` |
34+
| `sr-vic` | sentence cloze without listen button | `populate_blank` | `text` | `<p>{before} {{blank}} {after}</p>` |
35+
| `sel_r1-g_plusggg` | lead token before blank | `populate_blank` | `text` | `<p>{before_cloze_1} {{blank}}</p>` |
36+
| `sel_r1-_gplusggg` | short stem + blank | `populate_blank` | `text` | `<p>{before/after segments around {{blank}}}</p>` |
37+
| `sel_r1-_plusggg` | audio + choices only (no blank) | `audio_mc_only` | `text` | no blank token in template |
38+
| `sel_r1-gg_plusggg` | token-sequence style before blank | `populate_blank` | `text` | `<p>{before_1}{before_2} {{blank}}</p>` |
39+
| `sel_r1-_ggplusggg` | blank before trailing tokens | `populate_blank` | `text` | `<p>{{blank}} {after_1}{after_2}</p>` |
40+
| `sel_r1-s3_plusggg` | stimulus-heavy/image-first variant | `populate_blank` | `text` or `image` | include stimulus in `prompt`/`sentenceHtml`; keep single blank in `template` |
41+
42+
Notes:
43+
44+
- `choiceMode` should be `image` only when distractors are image choices; otherwise use `text`.
45+
- Current model contract is single-blank for `populate_blank`; dual-cloze source shapes are normalized to one `{{blank}}`.
46+
- `correctChoiceId` should always map from Learnosity `valid_response.name` (`distractor_n` -> `cN`).
47+
- Layout defaults are tuned from original CQT visual baselines, but they are not fixed: override through `model.layoutLimits` when host/theme requirements differ.
448

549
## Authoring model
650

751
- **`prompt`** (optional) and **`promptEnabled`**
852
- **`template`**: HTML string containing exactly one literal `{{blank}}` token
53+
- **`interactionMode`**: `populate_blank` | `audio_mc_only`
954
- **`choiceMode`**: `text` | `image`
1055
- **`choices`**: `{ id, labelHtml? }` or `{ id, imageUrl, imageAlt }` per mode
1156
- **`correctChoiceId`**
12-
- **`hasAudio`**, **`audioUrl`**, **`audioTranscript`** (optional)
57+
- **`hasAudio`**, **`audioUrl`**, **`audioTranscript`** (`audioUrl` required when `hasAudio=true`)
58+
- **`autoplayAudioEnabled`**, **`completeAudioEnabled`** (optional integration flags)
59+
- **`layoutLimits`** (optional): numeric visual constraints; defaults are based on current CQT parity behavior and can be overridden per item
60+
- **`layoutProfilePresets`** (optional): named preset map by `layoutProfile`; use profile as a template and override with `layoutLimits`
61+
- **`audioButtonSkin`** / **`audioButtonSkinsByLocale`** (optional): override listen-button skin URLs
62+
- **`uiText`** (optional): override labels/messages (show/hide correct, answer choices, autoplay prompt, transcript label, missing-audio message)
63+
64+
### `layoutLimits` keys (all optional, positive numbers)
65+
66+
- `blankStandaloneWidthRem`
67+
- `blankWideWidthRem`
68+
- `blankUnderlineWidthPx`
69+
- `blankUnderlineWideWidthPx`
70+
- `horizontalChoiceWidthPx`
71+
- `horizontalChoiceWidthVw`
72+
- `horizontalChoiceTileMinHeightRem`
73+
- `horizontalChoiceContentMinHeightRem`
74+
- `selectedImageMaxHeightRem`
75+
- `choiceImageMaxHeightRem`
76+
- `listenButtonSizePx`
77+
- `stimulusMinColumnPx`
78+
- `textMinColumnPx`
79+
- `legendMaxChars`
80+
- `choiceGroupGapRem`
81+
- `choiceRowGapRem`
82+
- `toggleButtonGapRem`
83+
- `horizontalChoiceRadioTopMarginRem`
84+
- `audioBlankTemplateMarginTopRem`
85+
- `audioBlankTemplateMarginBottomRem`
86+
- `stimulusGridColumnGapRem`
87+
- `stimulusGridRowGapRem`
88+
- `stimulusSentenceMarginTopRem`
89+
- `stimulusChoicesMarginTopRem`
90+
- `tokenGridColumnGapRem`
91+
- `tokenGridRowGapRem`
92+
- `tokenTemplateMarginTopRem`
93+
- `tokenInlineTokenGapRem`
94+
- `tokenChoicesMarginTopRem`
95+
- `inlineGridColumnGapRem`
96+
- `inlineGridRowGapRem`
97+
- `inlineTemplateMarginTopRem`
98+
- `inlineChoicesMarginTopRem`
1399

14100
## Session
15101

16102
- **`choiceId`**: selected choice id
17103

104+
## Implementation hints
105+
106+
- **Controller invariants:** in `populate_blank`, template must contain exactly one `{{blank}}`; in `audio_mc_only`, template must not contain `{{blank}}`.
107+
- **Choice normalization:** choices are polymorphic by `choiceMode` (text via `labelHtml`, image via `imageUrl`/`imageAlt`).
108+
- **Delivery rendering:** template is split around `{{blank}}`; selected choice content is rendered into the blank slot.
109+
- **Layout limits are model-driven:** delivery reads `model.layoutLimits` (blank widths/underline widths, choice tile sizing, image max heights, listen button size, layout column minimums); defaults are CQT-informed but overrideable.
110+
- **Evaluate mode behavior:** when evaluate/correct-answer mode is enabled, delivery can render `correctChoiceId` in the blank/choice state.
111+
- **Audio error behavior:** no TTS fallback is used; when `hasAudio=true` and no playable `audioUrl` is provided, delivery shows an explicit error message (configurable via `uiText.audioResourceUnavailable`).
112+
- **Print parity:** print view mirrors prompt/template/choice presentation with the same blank-token contract.
113+
114+
## Theming hooks (`pie-*` classes)
115+
116+
Delivery now exposes stable `pie-*` classes so hosts can theme this element with the same class-oriented approach used by other PIE elements.
117+
118+
- Root/container: `pie-element`, `pie-element-mc-populated-blank`, `pie-delivery-root`
119+
- Prompt/audio/template: `pie-prompt`, `pie-audio-container`, `pie-audio-player`, `pie-audio-transcript`, `pie-template-line`, `pie-sentence-line`
120+
- Blank display: `pie-blank-slot`, `pie-blank-slot-standalone`, `pie-blank-value`, `pie-blank-image`
121+
- Choice group: `pie-choices-fieldset`, `pie-choices-legend`, `pie-choices`
122+
- Choice rows/items: `pie-choice`, `pie-choice-horizontal`, `pie-choice-selected`, `pie-choice-label`, `pie-choice-image`
123+
- Choice controls: `pie-choice-radio`, `pie-choice-radio-inline`, `pie-choice-radio-bottom`
124+
- Evaluate/toggle feedback: `pie-toggle-correct-answer`, `pie-result-feedback`, `pie-choice-feedback-correct`, `pie-choice-feedback-incorrect`
125+
18126
## Builds
19127

20128
Same layout as `@pie-element/simple-cloze`: `delivery`, `controller`, `author`, `print`, plus IIFE bundle for script-tag loading.

packages/elements-svelte/mc-populated-blank/package.json

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,6 @@
3030
"development": "./src/print/index.ts",
3131
"types": "./dist/print/index.d.ts",
3232
"default": "./dist/print/index.js"
33-
},
34-
"./iife": {
35-
"default": "./dist/index.iife.js"
3633
}
3734
},
3835
"files": [

packages/elements-svelte/mc-populated-blank/src/controller/defaults.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,41 @@
11
/** Token authors must include exactly once in `template` (HTML). */
22
export const BLANK_TOKEN = '{{blank}}';
3+
/** Baseline values tuned to match historical CQT visuals; override via model.layoutLimits. */
4+
export const DEFAULT_LAYOUT_LIMITS = {
5+
blankStandaloneWidthRem: 7,
6+
blankWideWidthRem: 10,
7+
blankUnderlineWidthPx: 2,
8+
blankUnderlineWideWidthPx: 4,
9+
horizontalChoiceWidthPx: 170,
10+
horizontalChoiceWidthVw: 30,
11+
horizontalChoiceTileMinHeightRem: 11,
12+
horizontalChoiceContentMinHeightRem: 7.5,
13+
selectedImageMaxHeightRem: 4,
14+
choiceImageMaxHeightRem: 5,
15+
listenButtonSizePx: 128,
16+
stimulusMinColumnPx: 210,
17+
textMinColumnPx: 260,
18+
legendMaxChars: 120,
19+
choiceGroupGapRem: 0.5,
20+
choiceRowGapRem: 0.5,
21+
toggleButtonGapRem: 0.5,
22+
horizontalChoiceRadioTopMarginRem: 0.5,
23+
audioBlankTemplateMarginTopRem: 0.8,
24+
audioBlankTemplateMarginBottomRem: 1.8,
25+
stimulusGridColumnGapRem: 2,
26+
stimulusGridRowGapRem: 0.7,
27+
stimulusSentenceMarginTopRem: 0.2,
28+
stimulusChoicesMarginTopRem: 0.9,
29+
tokenGridColumnGapRem: 1.5,
30+
tokenGridRowGapRem: 0.8,
31+
tokenTemplateMarginTopRem: 0.6,
32+
tokenInlineTokenGapRem: 0.35,
33+
tokenChoicesMarginTopRem: 0.2,
34+
inlineGridColumnGapRem: 1.5,
35+
inlineGridRowGapRem: 0.65,
36+
inlineTemplateMarginTopRem: 0.45,
37+
inlineChoicesMarginTopRem: 0.25,
38+
} as const;
339

440
export default {
541
model: {
@@ -10,6 +46,21 @@ export default {
1046
interactionMode: 'populate_blank' as const,
1147
layoutProfile: '',
1248
choiceLayout: '',
49+
layoutProfilePresets: {},
50+
layoutLimits: { ...DEFAULT_LAYOUT_LIMITS },
51+
audioButtonSkin: null,
52+
audioButtonSkinsByLocale: {},
53+
uiText: {
54+
answerChoices: 'Answer choices',
55+
selectedAnswerInSentence: 'Selected answer in sentence',
56+
showCorrectAnswer: 'Show correct answer',
57+
hideCorrectAnswer: 'Hide correct answer',
58+
clickToEnableAutoplay: 'Click to enable audio autoplay',
59+
audioResourceUnavailable: 'Audio is enabled but no playable audio URL is configured.',
60+
transcriptLabel: 'Transcript',
61+
listenLabelEn: 'Listen',
62+
listenLabelEs: 'Escuchar',
63+
},
1364
sentenceHtml: '',
1465
template: `<p>The answer is ${BLANK_TOKEN}.</p>`,
1566
choiceMode: 'text' as const,

0 commit comments

Comments
 (0)