コース
コース
コース は、一連のページを案内付きの番号順シーケンス - レッスン1、レッスン2、 レッスン3…… - に変えます。自動生成される独自の番号付きインデックス、その コース自身に閉じた「Lesson N of M」の前後リンク(サイト全体のページ間の順序 とは独立しています)、そして読者が一度レッスンを開くと、そこから先は自分の ブラウザの中で進捗が記録されます: どのレッスンを完了したか、そして最後に 訪れたレッスンへ戻る「Continue where you left off」リンクです。
マニフェスト
docs/data/courses.yaml(.yml/.json も使えます - データファイル
を参照)ファイルを追加します。各トップレベルキーが1つのコースで、その
lessons 配列がそのコース自身のページを順番に並べます - 配列内の位置が
そのままレッスン番号 になります:
getting-started:
title: "Getting Started with BoxLang"
description: "A guided walkthrough from install to your first deployed site."
lessons:
- guides/course/introduction.md
- guides/course/windows-installation.md
- guides/course/mac-installation.md
- guides/course/creating-a-new-site.md
lessons の各エントリは docs/ 相対のパス文字列で、nav.json 自身が使って
いるのと同じ相対パスの規約です。レッスン自身のタイトル/サマリは そのページ
自身のフロントマター から取得され - マニフェストに複製されることはありません
- そのため、ページのタイトルをリネームしたり、サマリを編集したりすると、 コースインデックスに自動的に反映されます。複数のコースは、単に同じファイル 内に複数のトップレベルキーがあるだけのことです。
インデックス
Markdown のどこかに1行を置くだけで、そのコース自身の番号付きインデックスが レンダリングされます:
::: course id="getting-started" :::
これは本物のセマンティックな <ol> を1つレンダリングします - レッスンごとに
1つの番号付きリンクがあり、それぞれがそのレッスン自身のタイトルとサマリを
持ちます - さらに(最初は非表示で、読者が実際にコースを開始してから
クライアントサイドで埋められる)進捗バーと「Continue where you left off」
リンクが付きます。id のタイプミスや、レッスンがすべて揃っていないコースは、
ビルドを失敗させる代わりに、小さな目に見える注記に成り下がります。
レッスンページ
コースの lessons に列挙されたページはすべて、自動的に course コンテキスト
(page.course - コンテキスト変数
を参照)を、自分自身の位置・タイトル、そして そのコースに閉じた 前後リンク
とともに受け取ります - page.course.prevLesson/.nextLesson は、常にその1つの
コース内だけを移動します。これは、コースの有無にかかわらずナビツリー全体を
歩く、サイト自身のグローバルな
page.prevPage/.nextPage とは
異なります。レッスンは、自分がどのコースに属しているか、どこに属しているかを
申告する必要はありません - それを決めるのはマニフェストただ1か所であり、
あるページが誤って2つのコースに入ってしまうこともありません(それは執筆上の
ミスであり、ビルドが明確なエラーで検出します - エラー を参照)。
現時点では、bootstrap テーマだけが、この閉じたナビゲーション - 「Lesson N of
M」バッジ、コースに閉じた前後ページャー、「Mark complete」トグル - を
レッスンページ上に直接レンダリングします。他の組み込みテーマもすべて
page.course 自体は正しく計算します(そのため、それらのテーマを使っている
プロジェクトでも、独自のテーマオーバーライド
経由ですでにこれを表示できます)。残りの組み込みテーマにおけるネイティブな
表示はロードマップ上にあります。
進捗トラッキング
読者が一度レッスンを開くと、そのページ自身の隠しマーカーが
course-progress.js(すべての組み込みテーマで共有され、常に含まれています)
に訪問を記録するよう伝えます - 設定もオプトインも不要です。進捗は、その
ブラウザ自身の localStorage の中、bxsites-course-progress-<courseId>
というキーの下に完全に保存されます:
{
"firstStarted": "2026-08-30T14:02:11.000Z",
"lastVisited": { "url": "/guides/course/mac-installation/", "at": "2026-08-30T14:22:03.000Z" },
"completed": {
"/guides/course/introduction/": "2026-08-30T14:05:00.000Z",
"/guides/course/windows-installation/": "2026-08-30T14:12:44.000Z"
}
}
レッスンは、そのページが訪問された瞬間に自動的に完了としてマークされます。 「Mark complete」/「Mark incomplete」トグルを使えば、読者は誤って自動マーク されたものを取り消したり、後から戻ってレッスンを再マークしたりできます。 コースインデックスは、このデータをそのまま読み取って、チェックマーク、 進捗バー(「N of M complete」)、そして続きから再開するリンクを埋めます。
これは純粋なクライアントサイドの拡張であり、それなしでもすでに完全に機能 しているコースの上に重ねられているだけです - 番号付きインデックスも、 コースに閉じた前後ページャーも、どちらもサーバーサイドでレンダリングされる ため、JavaScript を無効にした読者や検索クローラーにも、基本機能一式 - 正しく番号が振られ、正しくリンクされたすべてのレッスン - がそのまま見えます。 進捗トラッキングは、コースが機能するために必須のものは何もありません。 ストレージがそもそも利用できない場合(プライベートブラウジング、サイト データのブロックなど)は、静かに「進捗は記憶されない」という状態に縮退する だけで、エラーにはなりません。
進捗はブラウザ単位のもので、背後にアカウントやバックエンドは一切ありません
- デバイス間で同期されることはなく、誰が何を読んだかというサーバーサイドの 記録もありません。それがプロジェクトにとって本当に必要な要件であれば、それは 今日のこの機能が対応する範囲の外です。
今後の拡張
この機能が意図的にまだ作り込んでいないものが2つありますが、既存の
courses.yaml を壊すことなく、将来そこへ育っていけるように形作られています:
- レッスン間のクイズ。 解決済みのレッスンは、内部的にはすでに
type(現時点では常に"lesson")を持っています - 将来のバージョンでは、lessonsのエントリとして、裸のパス文字列に加えて、小さなオブジェクト (例:{ path: ..., type: "quiz" })を受け付けられるようになる可能性が あります。裸のパスだけを列挙しているコースには何の変更も必要ありません。 - コース末尾の最終テスト/評価。 マニフェストのスキーマは、コースごとの
任意のトップレベルキー
finalTestを予約しています(現時点では無視され ます)- これは、後からスキーマの破壊的変更なしにこの機能を追加できる ようにするためだけのものです。自分のマニフェストの中で、このキーを他の 用途に使わないでください。
なぜフロントマターではなくマニフェストなのか?
コースのレッスンは、courses.yaml の中でただ一度だけ宣言されます - 各
レッスン自身のフロントマターに散らばる course: getting-started のような
フィールドとしてではありません。ページ単位のフィールドは、マニフェストと
並ぶ、強制力のない第二の信頼できる情報源になってしまいます。そして両者が
食い違うこと(あるレッスンのフロントマターは1つのコースを指しているのに、
マニフェストでは別のコースに列挙されている - あるいはどこにも列挙されて
いない)は、まさにこの設計が避けようとしている、静かなズレそのものです。
マニフェストこそが、コースの形 - どのレッスンを、どの順序で - を決める
唯一の場所です。レッスンページ自身が、自分がどのコースに属しているか、
どこに属しているかを知る必要は決してありません。
スコープ
lessonsが現在ビルド中のツリーの中で実在するページとしてすべて揃って いないコースは、そのツリーについては静かにスキップされます。ビルド失敗 にはなりません - これが重要なのは、docs/data/courses.yamlがプロジェクト 全体で一度だけ読み込まれ(データファイル がすでに 持っているのと同じスコープです)、すべてのバージョン/ロケールツリーで 変更なく再利用されるためです。裸のdocs/versions/<name>/スナップショット には、コースのレッスンファイルがまったく含まれていないこともあります。- レッスンは1つのコースにしか属せません - 同じパスを2つの異なるコースの下に 列挙するのは実際の執筆上のミスであり、例外が送出されます(エラー を参照)。
- この最初のバージョンでは、ネスト/マルチトラックのコースはなく、バージョン ごとに異なるレッスン順を持つバージョン別のコースマニフェストもありません。
エラー
BxSites.InvalidConfig-docs/data/courses.yamlの形に問題があります: コースの値がオブジェクトでない、titleが欠けている、lessonsがパス 文字列の空でない配列になっていない、または同じレッスンパスが複数の コースの下に列挙されている、のいずれかです。