
この記事を読んでわかること
- 「計画を立てて数値で押さえる」を、食事という一番サボりやすい領域で成立させる仕組み
- Power Apps Code Apps + Dataverse で作った実物の画面と、その裏のデータ設計(手札1枚が3役/記録は焼き込み/判定は目標レンジ)
- 実機で使ってみて「これは現実と合わない」と分かった仕様を、どう作り直したか(v1 → v4.2 の記録)
突然ですが、「なんとなく食べる」で体は変わりましたか?
筋トレをしていると、増量期・減量期という言葉が出てきます。ざっくり言えば「意図的に太る期間」と「意図的に絞る期間」です。筋トレはトレーニングだけじゃなくて食事も大事なんですね。
そして、食事管理の成功の秘訣ははっきりしています。メニューを固定化することです。
毎日その場のノリで選んでいると、カロリーもタンパク質も安定しません。「今日は何を食べるか」を毎回考えるのは、意志力の無駄遣いです。決め打ちにして、あとは実行するだけにする。
……のですが、ここで詰まりました。
減量が初めてなので、「決め打ちメニュー」を、どう組めばいいのか分からない。
鶏むね何グラム、オートミール何グラム、プロテイン何杯。それを朝昼夜に散らして、合計で1800kcal・タンパク質140g に着地させる。この組み合わせパズルを、私はスプレッドシートに書いたり、カロリーを調べたり、電卓で計算したり往復しながらやっていました。しんどい。
だから作りました。名前は「メシデッキ(MESHI DECK)」です。ソリューションを GitHub で配布しています(導入方法は記事の後半で)。
1. なぜ「カードゲーム」なのか
企画を考えていたとき、自分のメモにこう書いていました。
食品マスターに登録するものは、いわば自分が使える手札だ。 そして、メニューを決めたものが勝負する時のデッキのようなものだろう。 そして食べたかどうかの記録は、勝敗の実績のようなものだ。
これは単なる言葉遊びではなくて、そのまま設計になりました。
- 手札(HAND) = 食品マスター。自分が使える駒。ここに無いものは戦力にならない
- デッキ(DECK) = 期間メニュー。「減量1800」のように目標を決めて、手札から選んで積む
- 勝敗(TODAY / RESULTS) = 毎日の実食記録。食べたらチェック、その日の合計で判定
そして、使い切った手札は買い足す必要がある。買い出し(SHOP) です。

計画(デッキ)→ 実行(EAT)→ 判定(COMPLETE/未達)→ 補給(買い出し)が、1つのアプリの中で閉じています。これが「何かをやるためには計画を立てて数値で押さえる」の、私にとっての具体形です。
2. 手札 —— 1枚のカードが3つの顔を持つ
手札の画面です。トレーディングカードのステータス面をイメージして、kcal と PFC(タンパク質・脂質・炭水化物)を大きく出しています。

見ていただきたいのは、性質のまったく違うものが同じカードの形で並んでいることです。
- 「オートミール」= 素材そのもの(30g あたり105kcal)
- 「オーバーナイトオーツ」= レシピ(オートミール+プロテイン+豆乳+ブルーベリー)
- 「社食ヘルシーランチ」= 外食(毎日中身が変わる)
ここに至るまでに1回、設計をやり直しています。
「食べる単位」と「買う単位」でマスタを2本に分けたら破綻した
最初のバージョンでは、手札(食べる単位)とは別に「素材マスタ(買う単位)」を作りました。買い出しリストを出すには「1Lパックで買う」という購入単位の情報が必要なので、それは別テーブルだろう、と考えたわけです。
実際に自分のデータを入れ始めて、すぐチグハグになりました。
牛乳が、手札にも素材マスタにもいる。オートミールも両方にいる。同じものを2箇所で管理していて、どちらを直せばいいのか自分でも分からない。素材から手札を作るショートカットまで用意したのですが、それは構造の歪みを動線でごまかしていただけでした。

そこで、マスタを1本に統一しました。手札カード1枚が、3つの顔を持つ形です。

- 食べる —— デッキに積んで、EAT で記録される
- 材料になる —— 別のレシピカードの構成要素になる
- 買い出しに出る —— 購入単位(1Lパック=1000ml など)を入れておけば、買い出しリストに並ぶ
役割はテーブルで分けるのではなく、「その列を埋めたかどうか」で決まるようにしました。社食のように買い物対象でないカードは、購入単位を空にしておけば自然に買い出しから外れます。分岐を書かなくていいのが気持ちいいところです。
編集画面はこうなっています。「栄養の基準(この値は何あたりか)」が芯で、その下に材料、折りたたみで買い物情報。

3. レシピの栄養は、自分で計算しない
統一のいちばん大きな果実がこれです。
私は毎朝オーバーナイトオーツを食べています。オートミール30g、プロテイン1杯、無調整豆乳150ml、冷凍ブルーベリー50g、小分けナッツ1袋。これの kcal と PFC を、以前は電卓で足して手で入力していました。
材料が手札カードになったので、あとは合算するだけです。
1材料の寄与 = 使う量 ÷ 材料の基準量 × 材料の栄養
レシピの栄養 = Σ 全材料の寄与
これで 384kcal / P37.5 F12.6 C36.3 が自動で出ます。しかも材料側の値(たとえばプロテインを別の製品に変えた)を直すと、それを使っている全レシピが再計算されて保存されます。
ここで1つ、意地を張ったところがあります。
材料の栄養が1つでも空なら、自動計算しません。 「⚠️ 材料の栄養が未入力(手動値のまま)」のバッジを出して、正直に止まります。空欄を0として合計に混ぜてしまえば数字は出ますが、それは間違った数字が正しい顔で出てくるという一番よくない状態です。栄養の一般値を勝手に埋めるのも、私は嫌でした(自分が買った製品の値は、自分でパッケージを見るのが正しい)。
4. デッキ —— 積みながら数字が動く
ここがこのアプリの核です。「どう組めばいいのか分からない」への回答なので。

左が手札、右がスロット(朝/昼/夜/間食)。手札にチェックを入れて「まとめて積む」で流し込みます。

そして上の黒い帯。積んだ瞬間に合計 kcal と PFC が、目標対比で更新されます。
- 目標1800 に対して 1775、あと -25
- P は 134.5g / 140g で「残り5.5g」
- C は 199.3g / 177g で 「超過22.3g」——ここだけ赤くなります
赤は超過と削除にしか使わないルールにしました。色が意味を持っていないと、画面はただ賑やかになるだけなので。
バーの幾何も少し工夫していて、バーの全長は「実績と目標の大きい方」にしています。こうすると目標ラインの位置が動かず、超過したぶんだけ右に赤が伸びる。目標以下のときはボルトイエローが目標ラインに向かって伸びていく形になります。
5. 現実は、計画どおりに食べさせてくれない
ここから先は、実際に自分で使い始めてから分かったことです。企画段階では1つも思いついていませんでした。
(a) 出社の日と在宅の日で、昼が違う
会社に行く日は社食。家にいる日はおにぎりと味噌汁と卵。休日はまた別。
最初は「出社デッキ」「在宅デッキ」の2本を作って切り替えていましたが、これは間違いでした。デッキは「期間の勝負」の単位なのに、日ごとの都合で分けてしまうと、期間の集計がバラバラになる。
そこで1つのデッキの中に「日タイプ」を持たせました。カードに「毎日/出社/在宅/休日」のタグを付けて、TODAY の画面で「今日は出社」と宣言すると、その日に該当するカードだけが並びます。
デッキ一覧では、こう見えます。

1225〜1855 kcal。日タイプごとに合計が違うので、単一の数字ではなくレンジで出す。内訳(出社1775/在宅1855/休日1225)も添えます。
……これ、最初はバグっていました。編成画面が1726と言っているのに、一覧は2356と言う。原因は一覧側が日タイプを無視して全カードを合算していたからです。「毎日のカード+出社のカード+在宅のカード+休日のカード」を全部足した数字は、現実に存在しない日の合計でした。
(b) 社食は、日によって中身が変わる
「平均するとこのくらいのカロリー」は分かる。でも実測は違う。
これは「変動メニュー」フラグで受けました。変動メニューの手札は1タップ EAT ではなく、軽いシートを挟みます。

目安値(550kcal / P30 F18 C60)がプリフィルされているので、そのままでよければ確定を押すだけ。トレーに表示があった日は、その場で当日値に直して記録する。手間は1タップ増えるだけで、記録の精度が上がります。
(c) 週に1食は、好きなものを食べたい
これは「チート宣言」🔥です。記録するときに「これはチート」と宣言できます。
週1枠までは判定から除外されます。ただし枠を使い切った状態でもう1回宣言したら、「今週の枠は使用済み(この記録は判定に算入されます)」と正直に告げた上で、宣言自体は許可します。禁止はしない。ごまかしもしない。
そして画面の特大数字は、チート込みの実合計を出します。判定だけがチートを除外する。都合のいい数字を大きく出す作りにはしたくなかったので。
6. チェックは「行が存在すること」=食べた
データ設計でいちばん気に入っているところです。
食べたかどうかを表す Boolean 列(食べたフラグ)は作っていません。EAT を押したら記録の行を作り、チェックを外したら行を消す。それだけです。
真実がひとつの場所にしかないので、「フラグは true なのに記録が無い」みたいなズレが構造的に起きません。
そしてもう1つ。記録するときに kcal と PFC を数値としてコピーして保存しています(スナップショット)。
なぜかというと、手札の値は後から直るからです。プロテインを別製品に変えた、社食の目安を修正した、単位の基準を30gから40gに直した。そのたびに過去の記録の数字が全部変わってしまったら、記録の意味がありません。「先月は間違った数字で計算してたことになる」が起きないように、記録した瞬間の値を焼き込んでいます。
だから手札を削除しても、過去の実績は無傷です(画面にも「過去の記録は消えません」と書いてあります)。
7. 「勝ち負け」をやめた話
最初のバージョンの判定は、シンプルにこうでした。
予定メニューを全部消化したら WIN。
カードゲームのメタファーとしては綺麗です。実際に使ってみて、1週間で破綻しました。
ほとんどの日が LOSE になるのです。社食が変わる、チートする、そもそも夜に食べきらない。そして最悪だったのは、LOSE の理由が分からないことでした。数字は画面に出ているのに、「じゃあ何を直せばいいのか」が読めない。
作り直しました。

変えたのは2つです。
① 判定の基準を、消化率から「合計が目標レンジに入ったか」へ。
- 減量なら:判定kcal ≦ 目標
- 増量なら:判定kcal ≧ 目標
- 維持なら:|判定kcal − 目標| ≦ 目標の10%
- + タンパク質の目標があれば:判定P ≧ P目標(F と C は判定に使いません)
メニュー消化率は「参考」に降格させました。何を食べたかではなく、結果としてどこに着地したかで見る。自由記入も社食の実測も合計に入るので、これで自然に反映されます。
② 言葉を変えて、理由を必ず添える。
WIN → COMPLETE、LOSE → 未達。煽る赤枠をやめて、ニュートラルな黒枠にしました。そして未達のときは、その理由を必ず1行出します。

「P あと102.5g」。これです。ずっと LOSE だった正体は、たいていタンパク質の僅差の未達でした。それが表示されるようになった瞬間に、この画面は「責める画面」から「次の一手が分かる画面」になりました。
記録がゼロの日は「記録なし」です。負けではありません。 つけ忘れた日を負けにするアプリは、たぶん使い続けられません。
8. 買い出しリスト —— コピーしてスーパーへ
デッキが決まっていれば、必要な食材の量は計算できます。

計算はこうです。
必要枚数 = Σ(カードの数量 × その日タイプの日数)
素材の必要量 = Σ 材料の使用量 × 必要枚数
不足量 = max(0, 必要量 − 在庫)
購入数 = ceil(不足量 ÷ 購入単位の内容量)
「牛乳 必要1050ml/在庫— → 1Lパック ×2」。購入単位で切り上げるのがポイントで、1050ml 必要なら1パックでは足りないので2パック買います。
日タイプ別の日数はステッパーで指定します。プリフィルは TODAY で宣言済みの実績が入り、未割当の日数はごまかさず「未割当4日/全7日」と表示します。
買い出し期間はデッキの期間でクランプしています。デッキが終わった後の日数分まで買わせても意味がないので。
このタブの完成判定は、最初から1つだけ決めていました。「次の買い出しで、コピーしたテキストだけを見て買い物を完了できる」。 これを満たさない作り込みはしない、と。
9. 記録 —— 数値で押さえるということ
RESULTS タブです。COMPLETE率、カレンダー、kcal推移、PFC推移、デッキ別サマリ。

カレンダーの斜線は「記録なし」の日です。チート使用日には🔥が付きます。kcal推移では、チートで判定対象と実合計が乖離した日だけ2つの値が出ます。
スクショの COMPLETE率が 0% なのは、実データがまだ2日しかないからです(この記事を書いている時点で、作り直した判定ロジックで動き始めたばかり)。ここは正直に載せておきます。数字が育つのはこれからです。
10. 作り方の話(Claude Code に丸投げした部分)
技術構成はシンプルです。
- Power Apps Code Apps(React + TypeScript)
- Dataverse テーブル6本(手札/デッキ/デッキカード/記録/レシピ明細/日レコード)
- コネクタ0本、フロー0本(アプリとデータベースだけ)
作り方の面で書いておきたいのは、「実機で使う → 気づく → その日に直す」を何周も回したことです。
私が書いたのは仕様と裁定で、コードはほぼ Claude Code に書かせています。だから改修が速い。朝に「デッキ一覧の合計がおかしい」と気づいて、夜にはレンジ表示になっている、くらいのスピードで回りました。
バージョンの履歴だけ並べるとこうです。
| 版 | やったこと |
|---|---|
| v1 | 手札/デッキ/勝敗の3本柱。EAT のタップ1回、スナップショット |
| v1.1 | PC対応、EAT音、超過の赤、アクティブデッキ排他 |
| v1.2 | HAND に Excel 風グリッド(TSV ペースト対応) |
| v1.3 | 備考欄(レシピの作り方メモ)、手札削除 |
| v2.0 | 買い出しリスト(SHOP タブ)+ 素材マスタ ← ここで分けたのが失敗 |
| v3.0 | 日タイプ、チート宣言、変動メニュー、判定を目標レンジへ |
| v4.0 | マスタ統一(素材マスタ廃止)+ レシピ栄養の自動計算 |
| v4.1 | WIN/LOSE → COMPLETE/未達、未達理由の表示 |
| v4.2 | HAND のレイアウト圧縮(フィルタを1行に、ボタンをタイトル行へ) |
最後の v4.2 は純粋にレイアウトだけの改修ですが、これも実機発の指摘です。「フィルタが縦に4段あって、手札一覧に使える面積が狭い」。

そして自動検証を191項目書いています。EAT を押したら合計がちゃんと動くか、チェックを外したら戻るか、手札の値を変えても過去の記録が動かないか、390px 幅で横スクロールしないか、ボタンの背景と文字が別色か(黒地に黒文字を1回やったので)。
期待値は検証コード側に別実装で書き直して突合しています。アプリの計算式をコピーしてきたら、間違いも一緒にコピーされて何も検証していないことになるので。
……とはいえ、191項目 ALL PASS でもスクショの目視で不具合が見つかったことが何度もありました(削除ボタンが白いまま、**強調** の記号が画面に生で出ていた、など)。機械検査は目の代わりにはならないというのが、いちばん実感した教訓です。
11. 配布しています(GitHub)
「使ってみたい」という方のために、ソリューションパッケージを GitHub で公開しました。
このアプリは Solution MealDeck にまとまっていて、中身はアプリ本体と Dataverse テーブル6本だけです(コネクタもフローも使っていません)。なので配布はシンプルで、リポジトリの solutions/ にある zip を自分の環境にインポートするだけで動きます。エクスポート→別環境インポートで動くことは実機で確認済みです。
使う側の前提として、こうなります。
- Dataverse が使える環境が必要です(Power Apps のプレミアムライセンス。開発用なら無償の Developer Plan 環境でも動きます)
- データは付いてきません。 手札の中身(自分が食べているものの kcal と PFC)は、使う人がゼロから入れることになります
- そして正直に言うと、このアプリは私の食生活の形に最適化されています。社食があって、出社と在宅が混ざって、朝はオーバーナイトオーツ。ここが違う人には、たぶん別の形が正解です
なお、公開しているのはソリューション(ビルド済みのアプリ)だけで、ソースコードは含めていません。改造したい・ソースが見たいという方は、リポジトリの Issue で声をかけてください。
とはいえこの記事で持って帰っていただきたいのは、アプリそのものより設計の考え方のほうです。記録は焼き込む、フラグは作らない、判定には理由を添える、埋まっていない数字は正直に空にする。この4つは、たぶん食事以外の記録アプリでも効きます。
まとめ
食事管理アプリは世の中に山ほどあります。私が作ったものより、たぶんどれも高機能です。
それでも自分で作ってよかったのは、「自分が何を計画して、実際どうだったか」を数値で押さえる形を、自分の生活の形に合わせて決められたことでした。出社と在宅で昼が違うのも、社食の値がブレるのも、週1でチートするのも、全部私の事情です。既製品はそこまで面倒を見てくれません。
そして何より、この一連の作業でいちばん効いたのは、アプリではなく「メニューを決めて、数字で答え合わせをする」という行為そのものでした。1800kcal に対して1775。P はあと5.5g。この2つの数字が毎日見えるだけで、コンビニの前で立ち止まれるようになります。
何かを本気でやるなら、計画を立てて数値で押さえる。当たり前のことなんですが、当たり前をサボらないための装置を、私はどうしても作らないと用意できないタイプでした。
皆さんもぜひ、自分の生活の形に合った「押さえ方」を作ってみてはいかがでしょうか。
(ちなみに、こんなに立派な仕組みを作ったのに、この記事を書いている時点での COMPLETE率は 0% です。装置は完成しました。あとは食べる人間の問題です……)


