Zettelkasten Inbox / ブロック詳細
画面上の判断をNotionの書き換えに変える唯一の場所。16工程のうち実際に書くのは4つだけで、残りは「書いてよいか」と「狙いどおりに書けたか」の確認に費やされている。
抽象度 Lv.4(技術用語+システム上のやりとりまで/製品名・ライブラリ名は書かない)このアプリの心臓部。「Tagを選び、ABC Noteを選び、自分の言葉でコメントを書いた」という 画面上の判断を、Notionの実際の書き換えに変換するただ1つの場所。
やることは大きく4つ。 (1) 送られてきた内容が形として正しいかを16項目で確かめる。 (2) Notionの現在の状態と噛み合うかを確かめる。 (3) ABC Noteの本文「その他」へ、見出し+元ノートへのリンク+コメント+画像を転記する。 (4) 元のLiterature Noteの題名・コメント・タグ・ABC関連を書き換える。
そして最後に、書いた結果をもう一度読み直して、狙いどおりになっているかを照合する。 このブロックだけで、Notionへの往復が最低でも6回、多いと数十回になる。
7. 画像の添付 → 8. 保存:Notionへの書き込み一式 → 2番の控えを捨てて次のノートへ
created_section/appended/updated/already_present)POST /api/process。詳しくは5章の各工程に書いた409(通常モード)409。
さらに既存のTag・ABCを1つでも外そうとしていたら 409502すべての判断が揃うまで、1バイトも書かない。Tagを選んだ時点でも、 ABC Noteを選んだ時点でも、Notionには何も書かれない。 「保存」を押した瞬間に、すべての書き込みがここで一度に起きる。 途中で気が変わっても何も残らない、という単純さと引き換えに、 このブロックが背負う責任が非常に重くなっている。
7番を分けた理由がここで効く。画像を先に預けてあるので、 このブロックが受け取るのは短い番号と指紋だけ。 10MBの送信と、数十回のNotion往復が、同じ1回の要求に同居しない。
検査を3層に分けている。 (1) 形の検査(入口)=送られてきた内容が構造として正しいか。 (2) 状態の検査(Notionを読んだあと)=いまのNotionと噛み合うか。 (3) 結果の照合(書いたあと)=狙いどおりになったか。 この3層が、このブロックの骨格そのもの。
2種類の鍵を使い分ける理由。ノート単位の鍵は「同じノートを二重に処理しない」ため。 ABC Note単位の鍵は「同じ知識ページの本文を2方向から同時に書き換えない」ため。 別々のノートから同じABC Noteへ同時に転記することは普通に起きるので、 ノート単位の鍵だけでは足りない。
| 工程 | 主体 | やりとりの内容 | 方向 |
|---|---|---|---|
| 8-1 | ブラウザ → 保存API | ID群・題名・コメント・画像の番号と指紋 | → |
| 8-2 | 保存API(内部) | 16項目の形の検査 | ↻ |
| 8-3 | 保存API(内部) | そのノート専用の待ち行列への参加 | ↻ |
| 8-4 | 保存API → Notion | 預かり画像の状態・形式・大きさの再確認 | → |
| 8-5 | 保存API → Notion | 対象ノートの取得と、現在の関連の完全な把握 | → |
| 8-6 | 保存API(内部) | 処理済み/開いてから変更済み/関連の削除の判定 | ↻ |
| 8-7 | 保存API → Notion | 選んだTagとABC Noteの素性の再確認 | → |
| 8-8 | 保存API → Notion | (新規のとき)ABC Noteを本文ごと作成 | → |
| 8-9 | 保存API → 転記処理 | ABC Note1件ずつ、鍵を取ってから依頼 | → |
| 8-10 | 転記処理 → Notion | 本文を全部読み、「その他」の位置を特定 | → |
| 8-11 | 転記処理 → Notion | 既存なら書き換え、無ければ追記 | → |
| 8-12 | 保存API → Notion | 元ノートへ「マイコメント添付」として画像を貼る | → |
| 8-13 | 保存API → Notion | 題名・コメント・タグ・ABC関連の4項目を更新 | → |
| 8-14 | 保存API → Notion | (失敗時のみ)作ったABC Noteをゴミ箱へ | → |
| 8-15 | 保存API → Notion | 書いた結果の読み直しと照合 | → |
| 8-16 | 保存API → ブラウザ | 作成したABCの情報と、転記の結果 | ← |
8-1 保存ボタン(またはCtrl+Enter)を押すと、画面が持っている情報が一度に送られる。 画像は入っていない。番号と指紋だけ。
技術的には POST /api/process に
application/json。項目は、ノートID、TagのID配列、ABCのID配列、
新規ABCの題名と親TagのID、新しい題名、コメント、
画像の {uploadId, digest} の配列、
そして修正モードのときだけ reviewExisting と「開いた時点のID配列」2つ。
画面側でも保存できる条件を判定しているが
(Tagが1つ以上、ABCが1つ以上か新規題名あり、題名とコメントが空でない、画像が全部添付済み)、
サーバーはそれを一切信用せず全部やり直す。
8-2 まず、送られてきた内容の「形」だけを16項目にわたって確かめる。 Notionにはまだ触れない。
技術的には ID群は
32桁の16進か 8-4-4-4-12 に一致するかを見て、
ハイフンの位置を正規化してから重複を除き、10件を超えたら不正。
画像は最大5件、番号がIDの形、指紋が64桁の16進であることを見て、
同じ指紋のものは1つにまとめる(同じ画像を2回選んでも1枚になる)。
とくに重要な検査が4つ:
(1) 既存ABCの選択と新規作成の同時指定は禁止(どちらか一方でなければならない)。
(2) 新規作成の親Tagは、選択中のTagに含まれていなければならない。
(3) 修正モードなら「開いた時点」の情報が必ず添えられていること、
逆に通常モードでそれが添えられていたら不正(どちらも 400)。
(4) 題名500文字以内、コメント12,000文字以内。
(3) の「余計なものが付いていたら断る」という検査は珍しく、
画面の状態とサーバーの理解がずれていないかを、両方向から確かめている。
8-3 形が通ったら、そのノート専用の順番待ちの列に並ぶ。 同じノートに対する保存や削除が既に走っていれば、終わるまで待つ。
技術的には ノートのIDを鍵にした約束の連鎖で作った待ち行列。 新しく来た処理は「前の処理が終わったら自分が始まる」という形でつながれ、 自分が終わったら次を解放する。前の処理が失敗しても、 その失敗は握りつぶして次を必ず進める(1つの失敗で列が止まらないように)。 これはサーバーのメモリ上の仕組みなので、実行環境が2つ動いていれば効かない。
8-4 画像の番号を1つずつNotionに問い合わせ直して、本当に預かられているかを確かめる。 画面から送られてきた番号を鵜呑みにしない。
技術的には GET /v1/file_uploads/{番号} を
1枚ずつ順番に。状態が uploaded で、
形式が image/ で始まり、大きさが10MB以下であることを確認。
1つでも外れたら 400 +「添付画像を確認できませんでした。」。
7番で確認済みのことを、なぜもう一度確認するのか――
画面を通さず直接このAPIを叩かれた場合、番号は偽れるから。
ここで取り直したファイル名が、実際に本文へ書かれるキャプションになる。
8-5 対象のノートをNotionから取り直し、いま何のTagと何のABC Noteに 結び付いているかを完全に把握する。
技術的には ページを取得して
本当にLiterature DBの下にあるかを確認(違えば 400)。
続いて関連づけをタグ側とABC側で同時に取り切る。
ページ取得の応答には関連が最大25件しか入らないので、
「続きがある」印が立っていれば専用の問い合わせで100件ずつ全部たどる。
ここで「いまの真実」を掴むのが、次の判定の土台になる。
8-6 いまのNotionの状態が、画面の前提と噛み合うかを判定する。 噛み合わなければ、書かずに断る。
技術的には モードで分岐する。
通常モード:タグ欄かABC欄が1つでも埋まっていたら
409 +「このノートはNotion側ですでに処理されています。」。
受信箱に出る条件が「両方空」なので、埋まっている=別経路で処理済み、という判断。
修正モード:2段階で見る。
(1) 開いた時点のTag・ABCの集合と、現在の集合が完全一致するか。
違えば 409 +「Notion側で更新されています。全体マップから開き直してください。」
――これが楽観的排他制御(先に鍵を掛けず、書く直前に「変わっていないか」を確かめる方式)。
(2) 現在のTag・ABCが、送られてきた選択にすべて含まれるか。
1つでも欠けていたら 409 +「現在のTagとABC Noteを維持してください。候補の追加はできます。」
――修正モードでは「増やす」ことはできても「減らす」ことはできないという制約。
知識の削除という取り返しのつかない操作を、この画面からはさせない設計。
8-7 選んだTagとABC Noteが、本当にそのDBのもので、 親子関係が正しいかを確かめる。
技術的には TagのIDを1件ずつ順番に取得し、
親がTag DBかを確認。1つでも違えば 400。
ABC Noteも1件ずつ取得し、親がABC DBであることと
親Tagがちょうど1つで、それが選択中のTagに含まれることを確認。
違えば 409 +「選んだTag配下ではないABC Noteが含まれています。」。
これは6番の絞り込みと同じ条件を、書く直前にもう一度確かめている。
画面の一覧が古い可能性があるため。
なお取得は直列なので、Tag3件+ABC2件を選べばここだけで5往復かかる。
8-8 「新しいABC Noteを作る」を選んでいた場合、ここでページを1つ作る。 中身は空ではなく、最初から目次と「その他」の見出しと、いま書いたコメントが入っている。
技術的には POST /v1/pages に、
親としてABC DBを指定し、🧠の絵文字、題名、親Tagの関連、
そして本文(children)を一緒に送る。本文の中身は3つ。
(1) 目次ブロック(今後見出しが増えたときのため)、
(2) 大見出し「その他」、
(3) 最初の記入=中見出しに新しい題名、段落に
元ノートへの「メンション」(Notion上でページを指す埋め込みリンク)+改行+コメント、
そして画像。
ページ作成と本文の書き込みを1回の要求にまとめているので、
「ページはできたが中身が空」という中途半端な状態が生まれない。
8-9 既存のABC Noteへ転記する場合、1件ずつ、そのページ専用の鍵を取ってから処理する。
技術的には ABC NoteのIDを鍵にした、8-3 とは別の待ち行列。 違うLiterature Noteから同じABC Noteへ同時に転記しようとしても、 本文の読み取りと追記が交錯しない。 複数のABC Noteを選んでいる場合は、1件ずつ順番に処理する(同時にはしない)。 なお 8-8 で作ったばかりのABC Noteは、すでに中身が入っているのでここは飛ばす。
8-10 転記先のページの本文を、先頭から最後まで全部読む。 そして「その他」という見出しがどこにあるかを探す。
技術的には 100件ずつ、続きが無くなるまで取り切る。
「その他」の判定は、見出しブロックの文字を
全角半角をそろえ、空白を全部消し、小文字にした形で比較する。
見出しなら大中小どのレベルでもよい。
2つ以上見つかったら 409 で止まり、
「Notion側で1つに整理してください。」と人に投げる――
どちらに書くべきか機械には決められないため。
見つかったら、次に同じレベル以上の見出しが現れるところまでが「その他」の範囲。
8-11 「その他」の中に、このノートからの記入がすでにあるかを調べる。 あれば書き換え、無ければ足す。
技術的には ここがこのブロックでいちばん凝っている。
(1) 節の中の探索:各ブロックのJSONを丸ごと文字列にして、
対象ノートのIDが現れるかを見る(メンションのリンクにIDが入っているため)。
見つかって、その1つ前が中見出しで自身が段落なら、
その2つを書き換える(新しい題名とコメントで上書き)。
これで同じノートを何度保存しても、記入が増殖しない。
(2) 画像の差分追加:7番で作った指紋から
zi_attachment=◯◯◯◯◯◯◯◯-(64桁) という目印を組み立て、
それが本文のどこかに現れるかを探す。前半8桁はノートIDから作った短い値で、
同じ画像を別のノートから貼ったときに別物として扱うためのもの。
この目印は、画像のキャプションのリンク先URLの中に埋め込まれている――
Notionのブロックに「印」を持たせる場所が無いための工夫。
まだ無い画像だけを、その記入の末尾に追加する。
(3) ページ全体の探索:「その他」の中に無かった場合、
ページ全体を入れ子の奥まで再帰的にたどって、
そのノートのIDが本文のどこかに出てこないかを調べる(最大5,000ブロック、見つかったら即打ち切り)。
見つかれば「すでにある」として何もしない。
「その他」以外の場所へ手で移動された記入を、二重に書かないための保険。
(4) どこにも無ければ、「その他」の末尾に追記する。「その他」自体が無ければ、
見出しごとページの末尾に作る。
結果は updated/already_present/appended/
created_section のいずれかで返る。
8-12 元のLiterature Noteのほうにも、同じ画像を貼る。 こちらは「マイコメント添付」という見出しの下。
技術的には 8-10・8-11 とほぼ同じ手順を、
見出し名だけ変えて元ノートに対して行う。中見出し「マイコメント添付」が
2つあれば 409、無ければ作る。指紋の目印で、すでにある画像は飛ばす。
つまり同じ画像が、元ノートと選んだABC Noteすべてに複製される。
Notionは同じ預かり番号から複数のブロックを作れるので、
実体が何倍にもなるわけではないが、表示上はそれぞれの場所に現れる。
8-13 最後に、元のLiterature Noteの4つの項目を一度に書き換える。 これで受信箱から消える。
技術的には PATCH /v1/pages/{id} 1回で、
題名・コメント・タグの関連・ABCの関連を同時に更新する。
4回に分けず1回にまとめているので、
「タグだけ付いてABCが付いていない」という中途半端な状態が生まれにくい。
題名とコメントは1,900文字ごとに分割して送る――
Notionが1つの文字列部品に入れられる上限が2,000文字なので、
12,000文字のコメントは7つの部品に分かれて1つの段落になる。
この更新が終わった瞬間に、このノートは「未処理」の条件(両方空)から外れる。
8-14 8-9 から 8-13 のどこかで失敗したら、 このとき作ったばかりのABC Noteだけをゴミ箱へ移す。
技術的には PATCH で in_trash: true。
ゴミ箱への移動自体が失敗しても、それは黙って無視して元の失敗を優先して報告する
(後始末の失敗で、本当の原因が隠れないように)。
取り消されるのはこの1ページだけで、
既存のABC Noteへ書いた転記や、貼った画像は元に戻らない。
「新しく作ったものは無かったことにできるが、既存のものへの書き足しは戻せない」という線引き。
8-15 書き終わったあと、もう一度そのページを読み直して、 狙いどおりになっているかを1つずつ照合する。
技術的には 照合するのは6つ。
題名が一致するか、コメントが一致するか、タグの件数が一致するか、
ABCの件数が一致するか、送ったIDが全部含まれているか、
そして新規作成した場合は親Tagがちょうど1つで指定どおりか、
そのABC Noteから元ノートへの引用関係ができているか。
1つでも外れたら 502 +「Notionへの保存結果を確認できませんでした。」。
ここで 502 が返っても、書き込みは取り消されない。
「保存できたつもりで実は違った」を利用者に知らせるための照合であって、
元に戻す仕組みではない。
8-16 成功したら、受信箱と全体マップの控えを捨て、結果を返す。 画面は次のノートへ自動で進む。
技術的には 200 +
作成したABC Noteの情報、最終的なABCのID一覧、
ABC1件ごとの転記の結果(新規節を作った/追記した/書き換えた/すでにあった)。
画面側は再読み込みをせず、手元のデータを自分で更新する――
処理したノートを一覧から外し、Tagの使用回数を1増やし、
新規ABCがあれば一覧の先頭に足す。そして次のノートを選ぶ。
修正モードのときだけは、全体マップの画面へ戻る。
これがこのアプリの体感速度を決めている――
1件終わるたびに 2番の重い読み込みをやり直していたら、連続処理は成立しない。
← 図は横にスクロールできます
502 が返ったとき、
Notionには書きかけの状態が残る。自動で直す処理はどこにも無い。
Notionを正本と決めた以上、直すのはNotion側で人が、という割り切りだが、
利用者には「何がどこまで書かれたか」が伝わらない。400 + 何が足りないかの日本語。
Notionへは1度も行かない。何も残らない。409 +
「画面を更新してください。」。何も書かない。409 +
「全体マップから開き直してください。」。何も書かない。409 +
「Notion側で1つに整理してください。」。
ただし、複数のABC Noteを選んでいて2件目でこれが起きた場合、
1件目への転記はすでに済んでいる。502 +「Notion側を確認してください。」。
書き込みはすべて残ったまま。受信箱の控えも捨てられないので、
画面を再読み込みすると処理済みのはずのノートがまだ残って見えることがある。502 になった書きかけの状態を、誰がいつ直すのか(自動で直す仕掛けは無い)※ 2026年8月14〜15日時点の zettelkasten-inbox のプログラムを読んで作成しました。過去の設計資料は参照していません。
※ 番号は全体図・この解説・シーケンス図で共通です。工程番号は「ブロック番号-連番」で書いています。