haruki256

記事とショートの公開状態を、フラグではなくフォルダの場所で決めた

記事とショートの公開と未公開を、front matter(ファイルの冒頭に書く設定の並び)のフラグではなく、フォルダの場所で決めることにしました。

未公開のものは content/drafts/ に置き、公開するときは content/posts/<年>/content/shorts/<年>/ へフォルダごと移動します。ファイルの中身は編集しません。

これまではフラグを見るためにファイルを開く必要がありました。いまは公開の操作がフォルダの移動だけなので、開かなくても状態が分かります。

スレッドを開く スレッドを閉じる 2

未公開フォルダの設定は content/drafts/_index.md の1か所にまとめています。

このファイルに cascade(配下のページへ設定を配るHugoの仕組み)で draft: true を書くと、配下がすべて未公開になります。1件ずつ書く必要はありません。

同じファイルに、ページ自体を生成しない設定(build.render: never)と、一覧に載せない設定(build.list: never)も入れています。これで drafts フォルダ自体もページとして出力されません。

ローカルで下書きを見るときは hugo server -D-D は下書きも表示するオプション)を使います。

これを記事にまとめました。

https://haruki256.com/2026/07/28/hugo-mock-content-environment/

haruki256

Flutterの入力ダイアログでは、ControllerをダイアログのStateに持たせる

結論

Flutterで TextEditingController を使う入力ダイアログを作るなら、Controllerはダイアログの State で生成し、State.dispose で破棄します。showDialog の完了後も終了アニメーション中は入力欄が残るため、呼び出し側ですぐ破棄すると、表示中の入力欄から参照されるおそれがあります。

僕が実装した入力ダイアログでは、呼び出し側が showDialog の完了を待ってControllerを破棄すると、終了アニメーション中に「破棄後に使われた」というエラーが出ました。Navigator.pop で返り値が確定する時点と、画面からダイアログが取り除かれる時点は同じではありません。

そこでControllerをダイアログの State で作り、ダイアログが画面から取り除かれるときに呼ばれる State.dispose で破棄するようにしました。これなら、入力欄が残っている間はControllerも残ります。

Controllerの寿命は、それを作った関数ではなく、最後まで参照するWidgetに合わせるのが判断の基準です。

haruki256

ファイル差分だけでSQLiteキャッシュを更新すると、削除したフォルダの警告が残る

結論

JSONファイルの読み込み結果をSQLiteへキャッシュするアプリで差分更新を実装するなら、ファイルの追加・変更だけをテストして終えてはいけません。フォルダ削除は差分として検出されず、古い警告が残ることがあるため、更新対象が0件になる削除ケースもテストに含めます。

指定フォルダ内のJSONファイルを読み、結果と警告を一覧表示するローカルアプリで、画面表示用データをSQLite(アプリ内に組み込めるデータベース)へキャッシュとして保存しています。僕の実装では、ファイル差分だけでキャッシュを更新したところ、削除したフォルダに紐づく警告が画面に残りました。そこで、更新時に警告の参照先を確認し、存在しない対象の警告を削除しました。

ファイルの削除は変更として検出できました。一方、フォルダの削除はファイルの変更として検出されないため、フォルダがなくなっても警告の行だけがSQLiteに残っていました。

対策として、キャッシュの更新時に保存済み警告の参照先が実在するかを確認し、存在しないファイルやフォルダの警告を削除します。変更されたファイルが1つもない場合でも、この確認は省きません。

このようなJSON読み込み・SQLiteキャッシュ・ファイル差分更新の構成を使うなら、フォルダ削除後にも古い警告が消えるかをテストする必要があります。差分更新のテストでは、ファイルを追加・変更する処理だけでなく、参照先が消えた後に警告を削除できるかも確認します。

haruki256

フォルダ再編で、ローカルHTMLの相対リンクが切れた

手元のHTMLを分野ごとのフォルダへ整理したら、ページ間のリンクがまとめて切れました。../別分野/index.html のように、今いるファイルからの位置でリンクを書いていたためです。

ファイルを移すと、リンクに必要な ../ の数も変わります。今後もフォルダ構成を直す前提だったので、置き場所を基準にするリンクをやめることにしました。

スレッドを開く スレッドを閉じる 2

フォルダ再編でリンクを壊さず、別の端末でも同じHTMLを開けるようにするため、/common/style.css のように、サーバが公開するフォルダを基準に書くURLへ変えました。

端末内の絶対パスは、Google Driveの同期先が端末ごとに違うため使えません。このURLも、HTMLファイルを file:// で直接開く方法では、プロジェクトのフォルダを基準にできません。

そこで、自分のPC内だけで動くサーバを用意し、自分のPCを指す 127.0.0.1 のURLで開く形へ変えました。使っていたWindows環境では、WindowsにあるPowerShellと.NETだけで動かせました。

これを記事にまとめました。

https://haruki256.com/2026/07/30/local-html-over-localhost/

haruki256

ショートを、一覧の流れを残したまま共有できるURLにした

僕のブログでは、ショートを1件共有するときも、共有先は個別ページではなくショート一覧にしました。URLには一覧内の特定位置を指すアンカーが付き、該当するカードを示します。

ショート一覧でカードを選ぶと、別ページへは移らず、ブラウザのアドレスバーにあるURLだけが変わります。選んだカードは枠で強調され、URLをコピーするボタンも同じ一覧内アンカーを返します。

共有されたURLを開くと、一覧が該当カードの位置までスクロールします。その前後には、ほかのショートもそのまま並びます。

スレッドを開く スレッドを閉じる 1

これを記事にまとめました。

https://haruki256.com/2026/07/29/hugo-posts-and-shorts/

haruki256

Hugoからの乗り換えに備えて、専用処理をテンプレートと設定にまとめた

Hugoを選びましたが、将来も使い続けるとは決めていません。僕は別のツールへ移るとき、記事本文とデザインまで作り直すのは避けたいと考えました。

そこで、記事本文はHugo固有の記法を入れないMarkdownで書き、Hugoのショートコードは使わないことにしました。装飾はCSS、動きはJavaScriptに分け、Hugoだけで動く処理はテンプレートと設定にまとめています。新しくHugo固有の機能を使う場合は、専用の処理をまとめた一覧へ追記します。

スレッドを開く スレッドを閉じる 3

ショートに続きの投稿を足すときも、Hugoのショートコードは使いませんでした。代わりに、本文へHTMLコメント(Hugoの画面に表示されない目印)を置いています。

テンプレートはコメントを見つけると、後ろの文章を続きの投稿として表示します。別のツールへ移す場合は、移行先にも投稿ごとに分ける処理が必要ですが、本文を書き直さずに同じ目印を使えます。

Hugoから移るときに何を作り直すのか、サイト全体を整理しました。

  • そのまま使うもの:Markdownの記事本文、CSS、JavaScript、画像やフォント
  • 作り直すもの:Hugoのテンプレートとサイト設定、目次や前後の記事へのリンクを自動で作る処理

Hugoだけで動く処理は、テンプレートと設定に集まっていました。僕はこの一覧を乗り換え時のチェックリストとして残し、一覧にないHugo固有の機能は使わないことにしました。

これを記事にまとめました。

https://haruki256.com/2026/07/26/hugo-dependency-three-layers/

haruki256

WordPressを残したままHugoへ切り替えた

haruki256.comの本番を、WordPressからHugoへ切り替えました。切り替え前にデータベースとファイル一式をバックアップし、Hugoの生成物は公開先とは別のフォルダへ展開しています。

WordPressは削除せず、public_html を別名に変えてからHugo側と入れ替えました。公開後は主要ページ、CSS、JavaScript、SVG、RSS、サイトマップ、404、HTTPSへの転送を確認しています。

問題があればHugoを別の場所へ動かし、WordPressのフォルダ名を public_html へ戻します。切り替える方法だけでなく、問題が起きた場合に戻す方法も先に用意しておきました。

スレッドを開く スレッドを閉じる 1
haruki256

WordPressの旧記事13本はHugoで公開しないことにした

WordPressから取り出した旧記事は、Hugoへ移したからといって、すべて公開し直す必要はありません。

このブログでは、公開されていた7本と旧下書き6本をMarkdownで手元に残し、13本すべてを非公開にしました。以前の公開URLは記録用の legacy_url に残しますが、Hugoの公開先には使いません。

新しい記事は /:year/:month/:day/:slug/ の形式で、slugを英小文字のkebab-caseに統一します。旧記事を将来書き直して公開する場合だけ、新URLと転送を設定します。

スレッドを開く スレッドを閉じる 1

これを記事にまとめました。

https://haruki256.com/2026/07/21/wordpress-to-hugo-url-design/

haruki256

HTML教材で復習先を探すため、学習項目の前提関係をJSONで持つ

結論

HTML教材で誤答した学習項目から復習先を探すなら、学習項目同士の「先に理解する項目から、後で学ぶ項目へ」という関係をJSONで持ちます。誤答した項目のIDだけでは、その項目より先に理解しておくべき項目のどれでつまずいたかまでは分からないため、関係を逆向きにたどって復習候補を出します。

「HTML教材の採点結果は、問題文ではなく学習項目IDに結び付ける」では、問題の正誤と学習項目IDを一緒に残す設計を書きました。ただし、誤答したIDから分かるのは、その問題が扱う学習項目です。

そこで僕は、学習項目同士の直接の前提関係をJSONで表しました。たとえば「関数」の前提が「変数」、「変数」の前提が「値」なら、関数の問題で間違えたときに、関係を逆向きへたどって変数と値を復習候補として並べます。

このJSONが示すのは、教材内で決めた学習項目の前後関係です。復習候補を整理するために使い、学習効果を測った結果としては扱いません。

スレッドを開く スレッドを閉じる 1
haruki256

HTML教材の採点結果は、問題文ではなく学習項目IDに結び付ける

結論

ブラウザで問題を解くHTML教材を作るなら、採点結果には正誤だけでなく、問題が扱う学習項目の固定IDも残します。問題文を後から直しても同じ項目の誤答として追えるため、復習対象を文章の比較に頼らず選べます。

「HTML教材の必須構成を、目視チェックリストからスクリプト検査へ移した」では、僕が章ごとのHTML教材を作り、各章に理解を確かめる問題を置いていることを書きました。その問題の採点結果を後から復習へ使うため、問題文とは別に、その問題が扱う内容を表す学習項目IDを持たせました。

たとえば関数を扱う問題なら、採点結果へ正誤と concept-functions を一緒に残します。

これなら問題文を読みやすく書き直しても、同じIDの結果を同じ学習項目として扱えます。問題が扱う学習内容そのものを変えるときだけ、新しいIDに分けます。

正誤はその問題の結果、学習項目IDは復習する範囲を特定する手がかりとして、役割を分けて残します。

スレッドを開く スレッドを閉じる 1
haruki256

HTML教材の必須構成を、目視チェックリストからスクリプト検査へ移した

僕は、ソフトウェアアーキテクチャなどを学ぶための教材を、1章につき1つのHTMLページとして作っています。各章は、見出しで学ぶ内容を分け、図で関係を示し、問題で理解を確かめ、復習リンクから前の項目へ戻れる構成です。

章の中から必要な要素が1つ抜けても、HTMLページ自体はブラウザに表示されます。そのため、章を追加するたびに目で探すだけではなく、必要な要素が欠けていたらエラーにするスクリプトを書きました。

スクリプトへ章のHTMLを渡すと、見出し・図・問題・復習リンクを順に探します。どれかが欠けた章はエラーになり、すべて含む章だけが検査を通ります。

この検査で分かるのは、必要なHTML要素が存在するかどうかです。画面上のカード配置や図の読みやすさは、別に画面を見て確認します。

haruki256

WordPressからHugoへの移行を始めた

haruki256.comを、WordPressからHugoへ移す作業を始めました。

WordPressに大きな不具合が起きたわけではありません。記事をMarkdownで手元に置き、gitで変更を追い、公開環境では静的ファイルだけを配信する形が、これからの運用に合うと判断しました。

調査時点の投稿は7本で、承認済みコメントは0件でした。移行コストがまだ小さい今のうちに、既存URLを保ったまま切り替えられるか検証しています。

スレッドを開く スレッドを閉じる 1
haruki256

RAGへ渡したノートが分かるよう、回答に参照ソースを表示した

僕は、検索結果を生成AIへ渡して回答を作るRAGを実装しました。質問に関連するノートの一部を、回答用の情報としてチャットモデルへ渡します。回答だけを表示すると、どのノートが候補に入ったのか分かりません。

そこで、回答の下に「参照ソース」欄を置き、検索で選ばれたノートのパスと見出しを表示しています。見出しがない場合は、ノートのタイトルを表示します。

参照ソースは、回答が正しいことの保証ではありません。どのノートが候補だったかを確認するための手がかりとして使います。

スレッドを開く スレッドを閉じる 1

これを記事にまとめました。

https://haruki256.com/2026/07/01/obsidian-note-search-rag/

haruki256

Obsidianノートを、キーワードと内容の近さの両方で探すことにした

僕は、Obsidianに蓄積したMarkdownノートを探すデスクトップアプリを作っています。名前や用語の一致だけでなく、内容の近さからも候補を出せるように、検索方式を一つに絞りませんでした。

実装では、BM25(単語の一致度で順位を付ける全文検索)と、ONNX形式のモデルで内容の近さを数値化するベクトル検索を使います。検索用のベクトルを用意できている場合は、同じ検索文を両方に渡します。二つの順位はRRF(複数の検索順位を一つにまとめる方法)で統合します。

ベクトルを利用できないときは、BM25の結果だけを返します。利用者に検索方式を選ばせず、一つの検索欄で扱う設計です。

スレッドを開く スレッドを閉じる 1

これを記事にまとめました。

https://haruki256.com/2026/07/01/obsidian-note-search-rag/

haruki256

ObsidianのZettelノートを、自分や子孫の下へ移動できないようにした

結論

Obsidianで一つの焦点に絞った考えをZettelとして残し、親子関係を階層IDで管理するなら、IDの移動機能では自分自身・子孫への移動を拒否します。利用者が指定した新IDが別のノートですでに使われている場合も、書き込み前に止めます。循環やIDの重複ができると、親子ツリーを一意にたどれなくなるためです。

「Obsidianの階層Zettel IDを移すときは、子孫IDの対応表を先に作る」では、親ノートを移す前に子孫までの新旧ID対応表を作る判断を書きました。

僕は、Obsidianで一つの焦点に絞った考えをZettelとして残しています。ノート先頭には、親子関係を表す一意のZettel IDを付けています。

このIDを管理するために自作プラグインを作りました。Obsidian標準の機能ではありません。

移動先が自分自身または自分の子孫なら、親をたどると元のノートへ戻る循環ができます。指定した新IDが移動元以外で使われていれば、複数ノートへ同じIDを割り当てる衝突になります。僕のプラグインでは、どちらも対応表をノートへ反映する前に拒否します。

これは、実行前に分かる不正な移動を止める処理です。書き込み開始後に保存が失敗した場合は自動で元へ戻らないため、実行前のバックアップと実行後のID照合は別に必要です。

スレッドを開く スレッドを閉じる 1
haruki256

Obsidianの階層Zettel IDを移すときは、子孫IDの対応表を先に作る

結論

Obsidianで階層Zettel IDを移すなら、親ノートのIDだけを書き換えず、先に子孫までの新旧ID対応表を作ります。親のIDだけを 1 から 3 に変えると、子と孫のIDが古いまま残り、親子関係がずれるためです。

「Obsidianの親子ノートIDを手作業で管理しにくくなり、自動採番プラグインを作った」では、階層IDの採番と重複確認を自作プラグインへ切り出した経緯を書きました。続く「Obsidianの親子ノートIDを自動採番し、子作成とツリー表示まで行うプラグイン」では、子ノートの作成とツリー表示までの設計をまとめています。

僕は、Obsidianノートの置き場所とは別に、考えがどのノートから派生したかをZettel IDで管理しています。1.a1 から派生したノート、1.a.11.a から派生したノートを表します。今回は、フォルダや分類番号ではなく、このIDが表す親子関係を別の位置へ移す話です。

僕の自作プラグインは、変更するノートとその子孫を集め、次の対応表を作ってからfront matter(ノート先頭のメタデータ)へ反映します。Obsidian標準の機能ではありません。

1       → 3
1.a     → 3.a
1.a.1   → 3.a.1

対応表は親から子へ順にたどり、新しい親IDに合わせて子のIDを付け直して作ります。13 へ移した後も、子は 3.a、孫は 3.a.1 という関係で保存されます。

プラグインは、表にあるノートを1件ずつ保存します。途中で失敗しても自動では元に戻らないため、実行前にVault(Obsidianのノート保管場所)をバックアップします。実行後は、各ノートのIDが対応表の新しい値と一致するかを見比べます。

スレッドを開く スレッドを閉じる 1
haruki256

Obsidianの親子ノートIDを手作業で管理しにくくなり、自動採番プラグインを作った

結論

Obsidianで11.aのようなIDを使ってZettelの親子関係を表すなら、次の子IDを選ぶ処理と重複確認は自動化できます。人は「どの考えから派生させるか」を決め、連番の管理をプラグインに任せれば、IDを考えの親子関係だけに使えるようになります。

僕のVault(Obsidianのノートをまとめる保管場所)では、自分の考えを一つの焦点へ絞ったノートをZettelとして残しています。1の考えから派生したZettelには1.a、さらにそこから派生したZettelには1.a.1というIDを付けます。

「NDCは主題、Zettel IDは考えの派生に使う」で書いたように、このIDの役割はノートの分類ではなく、考えの親子関係を残すことです。

Zettelが増えると、新しい子を作るたびに、次の三つを手作業で行う必要がありました。

  • 同じ親の子IDを調べる
  • 次に空いているIDを選ぶ
  • 既存のIDと重ならないか確かめる

考えを書く前にこの三つを毎回行うことが、手作業では負担になりました。

そこで、階層Zettel IDの管理だけを補助するObsidianプラグインを作りました。ツリーで親を選んで子を追加すると、プラグインが既存の直下の子IDを調べ、空いている最初のIDを付けたノートを作ります。たとえば1.a1.bがあれば、次は1.cです。

どの考えを親にするかと、新しいZettelに何を書くかは僕が決めます。プラグインへ任せたのは、その判断の後に繰り返していたIDの割り当て(採番)と重複確認です。

スレッドを開く スレッドを閉じる 1

これを記事にまとめました。

https://haruki256.com/2026/05/11/obsidian-zettel-id-plugin/

haruki256

NDCは主題、Zettel IDは考えの派生に使う

結論

Obsidianで1ノート1焦点のZettelを整理するなら、僕はNDC(日本十進分類法)を参考にした分類番号を、ノートの主題に使います。1から1.aのようなZettel IDは、どの考えから派生したかという親子関係に使います。主題と考えのつながりを、一つの番号で表さないためです。

僕のVault(Obsidianのノートをまとめる保管場所)では、Zettelを1ノート1焦点で残しています。Zettelは、自分の考えを一つの焦点へ絞ったノートです。

「外部資料から自分の考えまでを、3つのレイヤに分ける」では、Zettelを自分の思考を置くレイヤとして分けました。そのZettelを増やしていくと、ノートの主題と、どの考えから発展したかを別々にたどりたくなりました。

そこで、僕のVaultでは二つの番号へ別の役割を持たせています。

  • NDCを参考にした分類番号:そのノートが何について書かれているかを表す
  • 11.aのようなZettel ID:どの考えから発展したかを表す

ここでの分類番号は、日本十進分類法を参考にしてノートの主題を表す、僕のVault内の運用です。一方のZettel IDは分類には使わず、1.aなら1から発展した考え、という派生関係だけを表します。

同じ主題のノートでも、考えの派生関係が別ならZettel IDは別になります。反対に、ある考えから派生した流れが続いていても、焦点が別の主題へ移れば分類番号は見直せます。

この二つを分けておけば、主題に合わせてノートの置き場を変えることと、考えの親子関係を変えることを、同じ操作として扱わずに済みます。

スレッドを開く スレッドを閉じる 1
haruki256

読書の要約と、そこから生まれた自分の考えを分ける

結論

Obsidianで本や記事を読みながらメモするなら、要約・引用は外部資料のノートに残し、そこから生まれた自分の考えはZettel(1ノート1焦点で残す自分の考えのノート)へ分けます。出典と自分の解釈の境界を、あとからたどれるようにするためです。

僕はObsidianのVault(ノートをまとめる保管場所)を、外部資料と自分の考えの両方を残す場所として使っています。

「ObsidianのVaultを、情報の役割ごとに構成する」では、Vault全体の置き場を役割で分けました。その中でも、書籍や記事を読んだ記録は、次の2つを同じノートへ混ぜないようにしています。

  • 外部資料のノート:著者の主張、要約、引用、出典
  • Zettel:読んだ内容から、自分の考えを一つの焦点に絞ったノート

僕のVaultで読書由来のZettelに残すのは、本の内容を章ごとにまとめ直したものではありません。元資料を読んで生まれた自分の考えを、一つの焦点へ絞ったものです。

自分の考えが独立したときは、元の要約へ書き足さず、新しいZettelから考えの元になった外部資料のノートへリンクします。これにより、どこまでが資料の内容で、どこからが自分の解釈なのかを分けて読み返せます。

ただし、短い引用に一言だけ感想を添えた段階では、無理にZettelへ切り出しません。あとから単独で考え直したい焦点になったときに、初めて別のノートにします。

スレッドを開く スレッドを閉じる 1
haruki256

ObsidianのVault直下は、情報の役割ごとに分ける

結論

Obsidianで複数種類の情報を同じVault(ノートの保管場所)に置くなら、Vault直下は情報の役割ごとに分けます。主題だけで分けると、同じ分野の資料と自分の考えが混ざるためです。Zettel(1ノート1焦点で残す自分の考え)は、その中の一つの役割として置きます。

僕はObsidianを、Zettel専用ではなく、自分の情報をためて整理する場所として使っています。

同じVaultには、役割の異なる情報を置いています。

  • 書籍や記事などの外部資料
  • あとから参照する事実や用語
  • 試して得た手順や、整理した知識
  • 1つの焦点に絞って残した自分の考え
  • アイデア、計画、外部向けの成果物

これらを最初から技術、社会、芸術といった主題だけで分けると、同じ主題について集めた資料と、自分で考えたことと、実用できる形に整理した知識が同じ場所へ混ざります。

そこで、情報を何に使うかで、Vault直下の最初のフォルダを分けています。その内側で、必要に応じて「何について書かれているか」を主題として分類します。

この構成では、Zettelは自分の考えを1ノート1焦点で残す置き場です。Vault全体をZettel中心の構成にせず、ほかの情報と区別して扱うための一つの役割として使っています。

スレッドを開く スレッドを閉じる 1