この雑誌は六か月間、同じ文を繰り返し書いた。
オフライン・キャッシュ動作 — まだ開いている行
(2026年1月を振り返ってからそのまま移したものだ。) この編がその行を消す。
なぜこれが残っていたか
作るのが面倒だったからではない。オフラインは試すのが厄介だ。 接続がある状態を試すのは簡単だ — 回して動けばいい。ない状態を試すにはなくしなければならず、「ない」という状態が何種類もある。
このサンプルはそのうち最も悪い二つだけを扱う。
- 送る先がない。 ところが客は目の前に立っている。
- 送ったのに答えが来ない。 届いたかどうかパッドには知る術がない。
二つは反対方向の失敗につながる。一つ目を扱わなければ注文が消え、二つ目を扱わなければ二重に計上される。 キューを入れて一つ目だけ解決すれば二つ目が新しく生まれる — それがこの編の言いたいことだ。
レジがあるとき

till: connected
online: took 1, delivered 1, till has 1 (3.5k won)
平凡だ。コーヒー一杯を押し、レジが受け取った。
レジが消える
till: process gone — the pad is now on its own
プロセスを落とした。フードトラックならホットスポットが切れたということであり、パッドの側からは区別がつかない — 送ったものが答えを受け取らないという事実だけが同じだ。
そして注文を三件さらに受ける。

offline: took pad1-002 (sandwich) — nothing was sent
offline: took pad1-003 (coffee) — nothing was sent
offline: took pad1-004 (juice) — nothing was sent
offline: pad still took 3 orders, 3 waiting, ids pad1-002 pad1-003 pad1-004
画面が赤いのは誠実であるためだ。 送れていないことを隠せば、それはオフライン対応ではなくオフライン隠蔽だ。注文は受け続けつつ、何件がまだ行っていないかを人が見られなければならない。
アウトボックスがすべてだ
このサンプルの答えはクラスひとつだ。注文パッドはレジを直接呼ばない。
/// The order pad never calls the till directly. It writes into here, and here
/// tries to deliver. That one indirection is what lets the person behind the
/// counter keep working while the till is unreachable, and it is also what
/// makes the failure honest: the pad can show how many orders are still
/// undelivered, because the outbox knows.
その一枚が二つを同時に与える。働き続けられることと、何件溜まっているかを言えること。
規則は二つだけだ。
① idはここで、最初の試行の前に作る。
final p = Pending('$device-${(++_minted).toString().padLeft(3, '0')}', item);
レジがidを付ければ再送が新しい注文になる。そうすると客が二度計上される。リトライが安全であるためには、何をリトライするのかがリトライ前に決まっていなければならない。
② 順番に送り、最初の失敗で止まる。
} catch (_) {
// Still ours. Leave it at the front and stop — the ones behind it are
// newer and must not overtake.
break;
}
飛ばして後ろのものを送れば帳簿が朝を間違って書く。12時の注文が11時の注文より先に刻まれた帳簿は、ただの間違った帳簿だ。
そしてキューはファイルにある。
/// Where the queue lives between app launches. A queue that only exists in
/// memory is not a queue — closing the lid is the same as losing the orders.
final File spoolFile;