Zettelkasten Inbox / ブロック詳細
選んだTagの配下だけを候補にし、題名で9件まで削ってから、その9件の本文をサーバーが下読みして並べ直す。安い材料で削って高い材料で決める、という二段構えがそのまま形になっている。
抽象度 Lv.4(技術用語+システム上のやりとりまで/製品名・ライブラリ名は書かない)Tagが決まったあと、その配下のABC Note(知識ページ)のうちどれに書き足すかを選ばせる場所。 ABC Noteは題名だけでは中身が分からないので、候補の本文を先に読んで、 「主な章」「論点」の形で見せるのがこのブロックの仕事。
絞り込みは2段階。まずTagで絞り、次に題名だけで仮の順位を付けて上位9件にする。 その9件だけサーバーが本文を下読みし、返ってきた中身でもう一度並べ直す。 「全部の本文を読んでから選ぶ」のは重すぎるので、2回に分けて絞っている。
5. Tagの推薦と新規作成 → 6. ABC Note候補の絞り込みと下読み → 7. 画像の添付/8. 保存
POST /api/candidate-content。絞り込み自体はブラウザの中400)Tagの後ろでなければ成立しない。候補は「選択中のTagの配下」に限られる。 Tagが決まっていなければ候補は空になる。この順番が、このアプリの操作の骨格そのもの。
下読みを9件に絞っている理由。1件ごとにNotionへ1往復かかる。 Tag配下に50件あれば50往復で、待ち時間もNotionの回数制限も現実的でない。 題名という安い材料で先に9件まで削ってから、高い材料(本文)を使うという二段構え。
「親Tagがちょうど1つ」という厳しい条件。親Tagが0個や2個以上のABC Noteは、 ここでは候補に出ない(全体マップでは「要確認」として別枠に出る)。 どのTagの配下かが一意でないと、絞り込みの理屈が崩れるため。 8番の保存でも同じ条件を再検査していて、この前提はアプリ全体で守られている。
控えを6時間と長く取れる理由。期限だけでなく 「そのページの最終更新時刻」を鍵にしているので、 Notion側で書き換えられれば時間に関係なく無効になる。だから長くても古くならない。
| 工程 | 主体 | やりとりの内容 | 方向 |
|---|---|---|---|
| 6-1 | ブラウザ(内部) | 唯一の親が選択Tagのものだけに候補を限定 | ↻ |
| 6-2 | ブラウザ(内部) | 題名だけの仮の点数付けと上位9件の選出 | ↻ |
| 6-3 | ブラウザ → 下読みAPI | 候補9件までのID | → |
| 6-4 | 下読みAPI → Notion | 各ABC Noteの先頭100ブロック(3件ずつ) | → |
| 6-5 | 下読みAPI(内部) | 章・論点の抽出と照合用本文の切り出し | ↻ |
| 6-6 | 下読みAPI → ブラウザ | 説明文・照合用本文・打ち切りの有無 | ← |
| 6-7 | ブラウザ(内部) | 本文の一致を加えた並べ直し | ↻ |
6-1 まず、選んだTagにぶら下がっているABC Noteだけを残す。 Tagを2つ選べば、どちらかにぶら下がるものが候補になる。
技術的には 2番が返した一覧の中から、 親Tagの数がちょうど1つ、かつそのIDが選択中の集合に含まれるものを残す。 「ちょうど1つ」という条件が厳しく、親Tagを2つ付けたABC Noteは 受信箱の候補には一切出てこない。Tagを外すと、そのTag配下だったABC Noteは 選択からも自動で外れる(画面の状態が矛盾しないようにするため)。 「新しく作る」を選んだときの親Tagも、選択中のTagから外れたら自動で付け替わる。
6-2 残った候補に、題名だけを見た仮の順位を付け、上から9件に絞る。 すでに選んであるものは、順位に関係なく残す。
技術的には 点数は 選択Tagとの一致数×120(Tagを複数選んだとき、どのTagの配下かで差が付く)、 題名がそのまま本文に含まれれば120点、題名の重なり×70、 引かれた回数の常用対数×2。この時点では本文の点は0。 上位9件と選択済みのものを混ぜ、IDで重複を除いた集合が候補になる。 9件という数はサーバー側の上限(1回1〜9件)と一致している。
6-3 まだ中身を読んでいない候補があれば、そのIDをまとめてサーバーへ送る。 一度読んだものは、もう送らない。
技術的には POST /api/candidate-content に
application/json で {"permanentIds": [...]}。
サーバー側は1〜9件・全部がNotionのIDの形を確認し、外れたら 400。
そのあとハイフンの位置を正規化して重複を除く。
ブラウザ側は「まだ持っていないID」だけを送り、切り替えが起きたら取り消す。
6-4 サーバーは候補1件ずつ、そのページの中身を読みに行く。 ただし全部を一気にではなく、3件読んだら0.9秒待つ。
技術的には GET /v1/blocks/{id}/children?page_size=100。
続きの目印があっても追いかけない――先頭100ブロックだけ見る。
3件を同時に投げ、揃ったら次の3件へ進む前に0.9秒待つ。
これはNotionの1秒あたりの要求数の制限(おおむね毎秒3回)に合わせた意図的な間引きで、
9件なら「3件・待つ・3件・待つ・3件」で約1.8秒の待ちが入る。
2番が3つの取得を単純に直列にしていたのに対し、
ここは「同時に3つ、そのあと休む」という別の作戦を取っている。
6-5 読んだ中身から、章立てと論点を拾って1〜2行の説明文にする。 見出しが無いページなら、最初の段落をそのまま使う。
技術的には ブロックの種類で振り分ける。
大見出し(heading_1)=章を最大3つ(各46文字まで)、
中見出し(heading_2)=論点を最大2〜3つ(各62文字まで)。
12文字以上の段落も拾う。ただし
「その他」という見出しと、記号だけの行(- ● | など)は捨てる――
「その他」は8番が転記に使う定型の見出しなので、説明文に出しても情報にならないため。
URLは事前に全部取り除く(4番と同じ理由で、長いURLが照合を歪めるから)。
説明文は全体で220文字まで、照合用の本文は6,000文字まで。
見出しも段落も無ければ「本文はまだありません。関連Literature Note ◯件。」と出す。
結果は6時間・最大500件の控えに入り、鍵はそのページの最終更新時刻。
500件を超えたら古いものから順に捨てる。
失敗した場合は控えから外すので、次に呼べばやり直せる。
6-6 説明文と照合用の本文を返す。10分間はブラウザ側にも置いてよい、と伝える。
技術的には 200 +
private, max-age=300(本人のブラウザにだけ5分)。
中身は候補ごとに、説明文・照合用本文・最終更新時刻・
本文から取れたのか題名だけなのかの区別・打ち切りの有無。
「題名だけ」の場合、次の並べ直しで本文の点が入らないことが説明できるようになっている。
6-7 説明文が届いたら、候補の順番を付け直す。 ここで初めて本文の中身が順位に効く。
技術的には 6-2 の点数に 本文の重なり×115 を足して並べ替える。 この115という重みは、題名の重なり(×70)より重く、Tag一致(×120)に迫る大きさ。 つまり「題名は違うが中身が近い」ABC Noteが、 下読みのあとで上位へ跳ね上がることがある。これがこのブロックを作った目的そのもの。 検索窓に入力したときは推薦を止め、題名と説明文に対する単純な絞り込み(上位9件)に切り替わる。
400 +
「ABC Note候補は1回につき1〜9件を指定してください。」400 +
「ABC DBにない候補が含まれています。画面を更新してください。」※ 2026年8月14〜15日時点の zettelkasten-inbox のプログラムを読んで作成しました。過去の設計資料は参照していません。
※ 番号は全体図・この解説・シーケンス図で共通です。工程番号は「ブロック番号-連番」で書いています。