Skip to content

Releases

1.7.1

Choose a tag to compare

@github-actions github-actions released this 04 Nov 18:47
708a3da
  • .node-version and .tool-versions pin Node 26.13.0, the current LTS
    release, and the GitHub Actions workflows run on Node 26, including the deploy
    and course-check workflows a course project inherits. The requirement does not
    change: Node 24 still works, so a course author on 24 need do nothing.

  • The dependencies are upgraded to their newest releases, all within the range
    package.json allows: @vscode/vsce to 4.1.0, eslint to 10.14.0, globals to
    17.14.0, marked to 18.1.3 and turndown to 7.2.5, along with the transitive
    tree. marked's two releases fix parser bugs, so unusual markdown (emphasis
    delimiter runs, lazy lines in a blockquote, link labels) can render
    differently. The pages shipped in course/ render exactly as before, and a
    page already on Canvas changes only when a push next renders it. Run
    npm install after the upstream update, as the update script reminds you to.
    npm audit still reports advisories in the Docusaurus build and preview
    toolchain and in eslint and @vscode/vsce. npm audit fix still resolves the
    Docusaurus ones by nesting an older Docusaurus, and the only fix it offers for
    the other two is a downgrade, so leave it alone.

1.7.0

Choose a tag to compare

@github-actions github-actions released this 24 Oct 14:20
d3b1d90
  • A push puts the alert icons back when they are gone from Canvas. It used to
    trust .canvas-sync.json alone: an icon recorded under the current theme was
    taken to exist, whatever had happened to the file since. So after
    npx course reset-canvas followed by a push, without
    npx course reset-sync-state in between, or after deleting the
    /course-icons folder by hand in Canvas, every callout the push rendered
    pointed at a file that no longer existed and showed a broken image, while
    status reported the course in sync. Each run that renders a page, an
    assignment or a discussion now asks Canvas whether every recorded icon is
    still there, one request per icon, and uploads any that Canvas answers is
    gone, recording its new id. If Canvas cannot be asked, for a lapsed token or a
    network fault, the recorded icon is kept and a warning names it, so the push
    goes ahead as it did before.

    Only the pages a push renders pick up the new icons. After reset-canvas that
    is every page, because the push creates them all again. After a hand-deleted
    /course-icons, a page you have not changed since keeps its broken icon until
    you next edit and push it.

  • A selection export puts a page from a module subfolder at the same heading
    level as every other page. A TOC file, a list of paths and --flagged, on the
    whole course or with -m, all leave out the subfolder's own heading, but the
    page under it kept the extra level it has in a whole-module export. In a PDF,
    a Word document and an -f md file alike, such a page came out one level
    below the other pages, under a heading that was never printed: across several
    modules, a third-level heading straight under its module's title. Every page
    of a selection is now a second-level heading under its module, or a
    first-level heading when the selection covers one module. Whole-module and
    whole-course exports are unchanged: they keep the subfolder headings, with the
    pages one level below.

    /study-pack-build always exports from a TOC file, and its check pairs the
    raw and the compact pack page by page on those second-level headings, which is
    also where the verbatim: prefixes apply. A verbatim page in a subfolder
    escaped both, so the compact pack could condense it without the check
    noticing. Regenerate any study pack whose recipe lists a subfolder page with
    /study-pack-build.

  • export -m and export-toc -m take several modules, and take them by number.
    -m used to want the one full folder name, so exporting modules 3 to 5 as one
    document meant writing a TOC file. It now takes a folder name as before, a
    number (-m 03 or -m 3), a range (-m 03-05) or a comma list (-m 03,05),
    and several values in one go (-m 03 05), as sync, pull and status
    already did. The modules come out in course order, each once. A range skips a
    missing number; one that matches nothing, an unknown number or name, and a
    number two folders share are errors that name the culprit. Several modules
    export as one document titled after the course, with a first-level heading per
    module and their subfolder headings intact, to
    exports/<course>-03-04-05.pdf; one module exports exactly as before. Because
    -m now takes several values, export -m 03-css course/x.md reads the path
    as a module and stops with Not a module, and export course/x.md -m 03-css
    stops too, where both used to export the module and drop the path without a
    word. A module and item paths never combined in one export. The VS Code
    extension passes a full folder name and is unaffected.

  • sync -m, push -m, pull -m and status -m take modules by number too, in
    the forms export -m takes: a folder name, a number (-m 03), a range
    (-m 03-05), a comma list (-m 03,05) or several values. push -m used to
    take one folder name and no more; it now takes several modules, like the other
    three. A number finds whatever the full folder name would: a module under
    course/, one only the sync state remembers, such as a module deleted here,
    and on sync, pull and status a Canvas module not yet pulled. A number or
    range that matches nothing, and a number two folders share, stop the run with
    an error naming it. A full folder name works exactly as before, including the
    refusal when nothing answers to it, so a script or the VS Code extension
    passing one is unaffected.

  • move-module -m, rename-module -m, delete-module -m, new-item -m,
    movetomodule-item --to-module and build-glossary -m take a module by
    number. Each used to want the full folder name; -m 03 or -m 3 now finds
    the module whose folder starts with 03. These commands act on one module, so a
    range or list that selects more than one (-m 03-05, -m 03,05) stops with
    This command takes one module and names the folders it would have meant, and
    a number two folders share is refused the same way. An unknown name or number
    stops with Module not found, as an unknown name always did; build-glossary
    now words it that way too, in place of its own
    Module "…" not found in course/ directory. A number makes a typo easier to
    commit: delete-module -m 3 -y deletes whichever module is number 03, where a
    mistyped name failed. It names the folder it deleted, as it always has, and it
    deletes nothing on Canvas, so a commit brings the module back. new-item -m
    and --to-module no longer take a path below the module, such as
    -m 03-css/02-sub; -s and --to-subsection name the subfolder. The VS Code
    extension passes a full folder name and is unaffected.

1.6.0

Choose a tag to compare

@github-actions github-actions released this 24 Oct 12:34
d978b43
  • The tutorial's Setting Up pages say where Coursewright comes from. What
    You Need
    opened with the four installs and never named the project, and
    Your Course Project linked it only as "the Coursewright project page", so
    a visitor reading coursewright.md had to go back to the home page to find the
    GitHub address. What You Need now opens with the address and says you get
    Coursewright by copying that project rather than installing it, and Your
    Course Project
    spells the address out in its first step. A course syncing
    this repository to Canvas sees the two pages reported as modified locally
    once.

  • The website footer is set in course.config.yml instead of
    docusaurus.config.js. It used to read "Copyright © 2026" and nothing else,
    and changing it meant editing a file every upstream update overwrites. A new
    footer: block takes a notice of your own in one line of markdown (text,
    with {year} for the year of the build), a Creative Commons licence button
    linking to its deed (license, an SPDX identifier such as CC-BY-NC-SA-4.0),
    and a "Made with Coursewright" link to the project (credit). The buttons
    load from the Creative Commons press-kit mirror rather than shipping with the
    template, because they are Creative Commons trademarks. The footer is the
    website's alone: exports and Canvas do not carry it. The template's
    course.config.yml sets license: CC-BY-NC-SA-4.0 to match
    course/LICENSE.md. An existing course keeps its own course.config.yml,
    which has no footer: block, so its next build shows the same copyright line
    plus the new credit, and no button. To drop the credit, add footer: with
    credit: false; to show the button, add license: with the identifier of the
    licence in your course/LICENSE.md.
    The Site Footer has the details.

  • npx course setup asks for the website footer's notice and licence, after the
    export style. The wizard used to leave the new footer: block alone, so the
    one place a course states who wrote it and under which licence had to be found
    and edited by hand. It now offers the current notice as the default (answer
    none for no notice) and lets you pick the licence button from the seven
    Creative Commons licences or none, with a reminder to keep it in step with
    course/LICENSE.md. An entry is written only when your answer changes it, and
    every comment in course.config.yml survives. Two new flags answer the
    questions from a script: --footer <text> and --license <id|none>, which
    stops with an error naming the valid identifiers on anything else. The summary
    line after the run names the licence.

  • npx course setup writes the course title, tagline, language and
    theme at the top level of course.config.yml only. It used to rewrite the
    first line that set the key at any indentation, so a course with an active
    labels: block that overrides glossary.title and sits above the top-level
    title: had its glossary heading replaced by the course name, and the course
    name left as it was. Nested keys are now left alone.

1.5.4

Choose a tag to compare

@github-actions github-actions released this 24 Oct 10:04
7fe6f57
  • The dependencies are upgraded: dotenv to 18, @vscode/vsce to 4, and
    everything else to its newest release within the range package.json allows.
    .node-version and .tool-versions pin Node 24.22.1, the newest 24 LTS
    release. No command behaves differently. Run npm install after the upstream
    update, as the update script reminds you to, so the project runs on the new
    versions. npm audit still reports advisories in the Docusaurus build and
    preview toolchain; they wait on a Docusaurus release, and npm audit fix
    resolves them by nesting an older Docusaurus, so leave it alone.

1.5.3

Choose a tag to compare

@github-actions github-actions released this 02 Oct 06:42
6ad6184
  • A pull now downloads a file item's binary onto the path its file_ref names
    when that path is inside a _files/ folder under course/, and leaves the
    file_ref as written. It used to download into the _files/ beside the
    wrapper, under the Canvas file's name, and repoint the wrapper there. So a
    wrapper in a subfolder that names the module's own folder as
    ../_files/pack.md, the way /study-pack-build writes one, was pointed at a
    second copy in a new _files/ inside the subfolder, and a file_ref into the
    shared course/_files/ was pointed at a module-local copy. The filename is
    kept too, so a file renamed in Canvas no longer leaves the old copy behind. A
    file_ref that leaves course/ or points outside every _files/ folder is
    still repointed, because the bytes come from Canvas and could otherwise land
    over a page.

  • Pushing a file item whose binary you edited keeps its module item. Canvas
    gives new bytes uploaded under the same name a new file id and repoints the
    module item at it by itself, but a push read the new id as a renamed binary,
    deleted the module item and created another at the end of the module. So every
    edited binary cost its item the id, and with it any /modules/items/:id link
    and completion requirement, and the next status or sync read the move as
    Canvas reordering the module and offered to renumber the local files to match.
    A push now reads the module item after the upload and leaves one Canvas
    already repointed alone. A renamed binary still gets a new module item, now at
    the position the old one held.

1.5.2

Choose a tag to compare

@github-actions github-actions released this 01 Oct 14:33
3834c5a
  • A pull no longer rewrites a file item's wrapper that already says what Canvas
    says. A pull that brought down the binary regenerated the wrapper beside it as
    well, so one whose title and file_ref had not changed still came back
    normalised: title: 'Syllabus' lost its quotes, a file without a final
    newline gained one, keys were reordered and any body was dropped. The wrapper
    is now rewritten only when its title, its type or the path its file_ref
    resolves to would read differently. A file_ref into the shared
    course/_files/ folder is still repointed at the module-local copy, as
    before.

  • Publishing or unpublishing a module in Canvas no longer makes the file items
    in it read as changed on Canvas. Canvas locks a file when its module is
    unpublished and unlocks it on publish, and each lock moves the file's
    updated_at, which a file item's fingerprint hashed. So after one publish
    click the next pull or sync downloaded every binary in the module again and,
    before the entry above, rewrote its wrapper as well. A file item is now
    fingerprinted by the file's modified_at, which moves only when the file's
    content is replaced, and the newest conflict tiebreak reads the same
    timestamp, so a publish click no longer makes Canvas look newer than a binary
    replaced here.

    The first pull, push or sync after updating re-records the fingerprint of
    every file item in .canvas-sync.json and touches nothing else, so expect a
    diff in that file worth committing. A file item whose module was published or
    unpublished since the last sync reads as changed on Canvas that one time, as
    it would have without the update: the publish moved the updated_at the old
    fingerprint was taken from.

1.5.1

Choose a tag to compare

@github-actions github-actions released this 01 Oct 14:33
89ee693
  • Tables in PDF and DOCX exports size their columns to their content. A pipe
    table with a row longer than 72 characters took its column widths from the
    dashes in its separator row, which Prettier pads to the widest cell, markup
    and link URLs included: a column of numbers beside a column of links came out
    2% wide, and inline code ran out of its cell. filter.lua now resets the
    widths, so Typst sizes the columns and Word autofits the table.

  • A table column without explicit alignment is left-aligned in the PDF. It
    inherited the centring of the block pandoc wraps every table in; an explicit
    :---: or ---: still wins.

  • The Thomas More style no longer justifies text inside table cells. Its body
    text is justified, and in a narrow cell that stretched the spaces and
    hyphenated words, down to a two-word header split as "Metho-de".

1.5.0

Choose a tag to compare

@github-actions github-actions released this 18 Sep 16:42
3f9ac29
  • A heading whose text opens with inline code, ## `void` for instance,
    kept its level in every export, so a page's own sections sat at page level in
    a combined PDF, DOCX or markdown export. The mask that hides code from the
    heading shift blanked the code span along with the rest, and the shift then
    saw no text after the hashes. The mask now decides only whether the hashes are
    real; the text is read from the line itself.

  • A new skill, /study-pack-build, owns the study packs: one recipe per pack in
    sources/study-packs/, the raw export kept in raw/ next to it, and the
    compact pack the course ships condensed from that export per the skill's
    rulebook. A helper script checks that the compact keeps every heading, every
    code block and every verbatim section, and lists the sections a re-run has to
    re-condense. Packs were raw exports before, 125 to 190 KB each in a course of
    twenty lessons: heavy for a chatbot's context and for a student's free tier.
    .prettierignore now covers the recipes and the raw exports.

  • /ai-tutor-build no longer builds the study packs itself: it proposes them
    and hands the approved proposal to /study-pack-build, which skips its own
    approval gate when handed one. The AI module's intro page now says a pack is a
    condensed snapshot of the course.

1.4.0

Choose a tag to compare

@github-actions github-actions released this 16 Sep 18:08
2c41735
  • PDF exports no longer split what reads as one thing. An alert, a code block or
    a table up to half the page's text area, and a list up to a quarter, moves
    whole to the next page instead of breaking; a taller one still breaks, with
    Typst's own guard against a lone first or last line. An alert that does break
    keeps its title with the body; the generic style's H2 rule and both styles'
    H5/H6 labels are sticky like every other heading; and filter.lua keeps a
    paragraph that ends in a colon or is at most three words long ("Voorbeeld:",
    "Bijv.") with the code, list, table or alert it introduces. A table taller
    than the page used to run off it, because pandoc wraps every table in a figure
    and Typst does not break figures: it now breaks with its header row repeated.
    The keep-together helper in template.typ takes a ratio, so a forked
    template can tune the threshold. The price is a few more pages, since a block
    that moves whole leaves a gap above the break.

  • Heading numbering is off by default, in both export formats and both shipped
    styles; export.number_headings: true in course.config.yml turns it on.
    Every module or page title that carried a number of its own came out doubled
    ("1. 1. Je eerste website", "5. 5a. CSS"), because template.typ numbered
    headings in Typst and reference.docx numbered Heading 1-4 natively, and a
    --flagged export numbered items "1.0.1." where it drops a subfolder heading.
    With the key on, pandoc's --number-sections numbers both formats the same
    way, "1", "1.1". The native numbering is gone from both shipped
    reference.docx files; a forked style keeps its own copy and should drop the
    numPr from its heading styles too, or the DOCX doubles again with the key
    on. --var section-numbering=1.1. still numbers a single PDF run with a Typst
    pattern of your choice.

  • The style sample rendered its alerts, link card, attachment and page break as
    literal ::: {...} text: an earlier Prettier pass had joined every fenced div
    onto one line, which pandoc does not read as a div. The fences are back on
    their own lines and export-styles/sample.md is in .prettierignore, since
    Prettier's markdown parser has no notion of a fenced div.

  • .github/workflows/release.yml publishes the GitHub release when a v* tag
    is pushed, titled with the version and carrying that version's CHANGELOG.md
    section as the notes. Step 5 of the release procedure was the only step with
    no command attached, and both 1.2.0 and 1.3.0 were tagged without a release to
    show for it, so pushing the tag is now the whole of it. It is gated to this
    repository the way test.yml is, so a course project that tags its own
    material hears nothing from it. scripts/changelog-section.js cuts the
    section out and fails loudly when the changelog has none for the version, and
    npm test now checks that the version in package.json has a section at all,
    which catches a skipped step 1 before the tag is cut rather than after it is
    public.

1.3.0

Choose a tag to compare

@lars-derichter lars-derichter released this 10 Sep 20:23
42933dc
  • The theme comment in the shipped course.config.yml pointed at
    docs/customization.md; the guide is docs/customisation.md. A course made
    from an earlier release keeps its own copy of the file, so fix the path there
    by hand if it bothers you.

  • Course: Check in the command palette runs npx course check in the shared
    terminal, the way Course: Validate does. Palette only, like Course:
    Build Glossary
    : no entry in the Course Manager tree or its menus.
    check-links has no entry of its own, since Check runs it.

  • npx course check runs every pre-ship check in one go and exits non-zero
    naming the ones that failed: validate; build-glossary --check when the
    course keeps a glossary at the default path, skipped and said so when it does
    not, since the glossary command fails without one; check-links; and every
    entry of checks.extra in course.config.yml. Each runs as its own process,
    so one failure does not stop the rest. npm run hooks:install installs it as
    a git pre-push hook, and .github/workflows/course-checks.yml runs it in
    place of validate alone, so a course's CI now covers glossary freshness, the
    plain link check and the course's own extras as well.

  • npx course check-links reports every relative markdown or image link whose
    target does not exist on disk, under the folders checks.links.roots names
    (course alone by default) or a repeated --root. validate resolves the
    links between the items it scans and nothing else, so course/index.md,
    course/LICENSE.md, a folder nested deeper than a module takes and any
    markdown kept outside course/ went unchecked; this is the plain filesystem
    check beside it. Code blocks and inline code are ignored, external URLs and
    site-absolute paths are left alone, and the run lists every broken link and
    exits non-zero when there is one.

  • course.config.yml takes a checks: section for the pre-ship checks.
    links.roots lists the folders check-links scans for broken relative links,
    course alone unless a course says otherwise; extra lists the subcommands
    check runs after its built-in list, so a course that registered a command of
    its own (a games build, say) has it gated by the same command and the same
    pre-push hook without patching the tooling after every update. A value of the
    wrong shape warns and falls back, entry by entry where it can, like every
    other key in the file.