日付入力欄の年越しトラップ:保存する前にその年を読み戻す
站点里有个页面叫 /masturbation,公开展示每天的次数和一张年度热力图,后台 /dash/masturbation 供管理员补记、修改、清除。数据很私人,页面很普通:一条按年查询的 RPC、一张日历、一个日期输入框加一个次数输入框。 上线当天就出了个会静默吃掉数据的 bug。 0 既是「这天没有」,也是「把这天清零」 热力图按年加载,编辑器的 selectedCount 从当前这一年的查询结果里取。管理员在 2025 年的页面上,把日期改成 2024-03-12—— 输入框里的日期变了,但页面还是 2025 年的数据; selectedCount 在 2025 年的数组里找不到这一天,回落到 0; 次数输入框显示 0; 点保存,写入 0。 如果 2024-03-12 原本记了 7 次,就被这一次「什么都没改」的保存抹掉了。0 在这里同时是「没有记录」的展示默认值和「清除记录」的写入值,两者长得一模一样。 让日期自己带上年份 第一反应可能是加一句提示「跨年日期请先切换年份」。但更可靠的是把年份变成选中状态的一部分: 选中一个不属于当前年的日期时,直接 navig...
Vercel から移った後、プラットフォームが代わりに支えていた部分が一つずつ露わになってきた
9月29日から30日までの2日間、21回のコミットで nav-site を Vercel + Neon からシンガポールの VPS へ移行した。Cloudflare Tunnel で入り、オリジンサーバーはパブリックポートを一切開けず、postgres は 127.0.0.1:5432 のみにバインドし、アプリは Docker Compose で動かし、ビルドもサーバー上で専用の buildx ビルダーを使って行う。 移行自体は最終的に成功した。しかし、それまでのコミットはほぼすべて同じ類のものを補うものだった。これまでプラットフォームが代わりにやってくれていた前提である。この記事は発見順に記し、コミット順には沿わない。 一度のジャンプで 0.0.0.0:3000 に飛ぶ 本番切り替え前にプレビューで実測すると、/r/<渠道码> の 302 のリダイレクト先は https://0.0.0.0:3000/ で、Google ログイン成功時もエラー時の戻り先も同じだった。公開直前に初めて気づくようなことがあると、サイト全体の短縮リンクが一斉に無効になる。 根本原因は n...
レート制限が書き込み行の前に立ちはだかる:確認コードが自ら無効化された一件の調査
9月の認証マイルストーンでは、メール認証コード、パスワードリセット、Google ログインをまとめて仕上げた。レビュー1回目で指摘されたものの中にレート制限に関する項目があり、これが「数字をいくつか設定する」話とは全く別物だったので書き残しておく。 まずコードの順序を見る better-auth の email-otp プラグインは、エンドポイント内でまず resolveOTP(email-otp/routes.mjs:31)を呼び、無条件で検証コードを1行挿入する。送信を行う sendVerificationOTP コールバックはその後に実行される。一方、消費時には consumeVerificationValue(internal-adapter.mjs:818-860)が常に「その identifier の最新の1行」だけを対象とし、消費時に全行を deleteMany する。 旧コードはレート制限の判定を送信コールバックに、つまり挿入の後にぶら下げていた。一度ヒットすると: resolveOTP はすでに新しいコードを1行書き込んでいる; コールバックがレート制限を判...
クライアント CSV こそが唯一の真実の情報源:C1 から C68 まで、数値を一つずつ返していく
まず背景から。これは私自身の『Boom Beach』プライベートサーバーで、クライアントのバージョンは 57.135。サーバー側に加えて、ブラウザ上で基地のレンダリングと編集を再現する webclient もある。クライアントはパッケージ内に一整套の csv/ を同梱している——建物、障害物、装飾、トラップ、词条、globals、さらには「この岩を除去するのに金貨がいくら必要で、木材がいくつ落ちて、司令部が何レベル必要か」まで全部そこに書かれている。 以前はこれらのものをエンジンとシェルの両方にそれぞれ写していた。ここ十日で写した部分を一つずつ消し、直接テーブルを読む形に変えて、番号は C35 から C68 まで並んだ。この記事はコミット順に復唱するつもりはなく、記録に値するいくつかの事だけを拾う:なぜテーブルを読まなければならないのか、「読んだかどうか」をどうやって赤くなるテストにするのか、そしてテーブルにないものをなぜ勝手に作ってはいけないのか。 手書きの代償は「一度写し間違える」ことではない 手書きの最悪の形は写し間違いではなく、正しく写した後にそれがすでに古くなっている...
AIは機能を書けてもDockerfileはうまく書けない:Next.jsセルフホストのつまずき記録
この記事で参照している公式ドキュメント:Next.js 自托管、Serwist、PM2 配置项、postgres 镜像说明 最近続けて三、四件の Next.js プロジェクトをセルフホストしたので、そこで踏んだ穴をいくつか記録しておくね。機能の部分は Claude が書くのが本当に速くて、ページも tRPC ルートも Prisma スキーマも一気に作れて、そのまま動くんだ。でもデプロイの仕上げになると、同じファイルを何度も書き換えては戻してを繰り返す。以下は全部「build は通るのに動かない」系の話で、ローカルではまったく兆候すらなかったやつら… 前置きはいいから、本題いこう! standalone マルチステージイメージ:実際に穴を踏んだのは一つだけ 本当はタイトルを「四つの穴」にしようと思ったんだけど、git の履歴を見たら顔を叩かれた~ vns-next の Dockerfile はライフサイクル全体で 4 回のコミットでしか触られていない:404a769 で作成、その後 0b7117c、037ccf4、fefd366 でそれぞれ一回ずつ修正。で、以下の項目のうち三つ...
複数のAIが一緒にコードを書くとき、私が引いた境界線
最近、いくつかのプロジェクトで同じリポジトリが二人の AI コーディングツールによって同時に変更され、さらに何人かが自分でコードを push していた… 最初は別に気にしていなかった。どうせコードを書くだけだし、と思っていたが、一通りやってみて分かったのは、問題はほぼコード自体になく、「誰がどのルールを読んだか」「誰がマシン上の何に触れられるか」といった協業の仕組みのほうにある、ということだった。ここに最終的に引いた境界線をいくつか記録しておく。 まずは逆転劇:いわゆる「多方」 別のリポジトリを見返していて面白いことに出くわした。git shortlog -sn を実行すると二行、二人の名前が出てきて、二人が push しているように見える。メールアドレスを調べると同一人物で、余分な一人は私が Web 画面で merge をクリックしたときのアカウント名だった。そして 78 個の commit のうち 63 個に Co-Authored-By: Claude が付いている——本当の「多方」は複数人ではなく、一人といくつかの AI セッションなのだ。並行作業の痕跡も .claud...
9 個のコミット、人間の作者は一人もいない:AI が独力で一つのプロジェクトを書き終えたその後
最近、人が投げてよこしたプロジェクトを引き継いだ。git log を一発叩いて少し固まった——9 つの commit、author は全部同じ AI Dev <ai-dev@local> で、人間のコミットは一つもない。以下に挙げる commit は hash を貼らないので、やった内容で呼ぶことにする。リポジトリには CLAUDE.md も AGENTS.md も .claude/ も .cursor/ もなく、CI もない。lint すら tsc --noEmit だけで、テストに至ってはゼロだ。一日かけて読み漁って、うまくやった点とやらかした点を記録しておく… まずこのプロジェクトが何なのかを説明しておこう。純クライアントの React SPA で、バックエンドは Express を Vercel Function の単一エントリに打包したもの。api/index.ts と api/lib/ の下に六つのモジュール、データベースは Neon Postgres、オブジェクトストレージは S3 互換 API を使っている。これだけのもので、複雑ではない。 まず、う...
Next.js レンダリングモード調査メモ:force-dynamic、ハイドレーション、そしてビルド時にデータベースがない件
最近、Next.js のプロジェクトを立て続けにいくつか書いていて、機能自体はどれも順調だったのに、全部同じ種類の問題で詰まってしまった。ローカルの next dev では何の問題もないのに、build した途端、あるいはデプロイした途端に事故る。こうした罠はどれも「コードの書き間違い」ではなく「レンダリングモードを間違えている」類のもので、しかもエラーメッセージはたいてい別の場所を指しているので、とにかく時間を食う… ここでは自分が踏んだものをいくつか記録しておく。できるだけファイルの行番号も書いておくね~ 罠1:ビルド時にはデータベースが存在しない vns-next はギャルゲーのリソースサイトで、Prisma + Postgres、デプロイは Docker Compose によるセルフホスト。ローカルでは問題なく動くのに、Dockerfile のマルチステージビルドに入った途端に爆発する。原因は、Next がデフォルトで next build の段階で RSC ページを静的に事前レンダリングするからで、builder のレイヤーにはデータベースのコンテナがそもそも存在しな...
Claude にデータ層のミスを減らさせる:Prisma、Drizzle と数条の構造化されたやり方
ここ最近のいくつかのプロジェクトでは、Prisma、Drizzle、そしてORMを使わずに直接SQLを書く、という3つのやり方をそれぞれ使った。 同じAIでも、この3つで犯すミスはまったく違う。これはけっこう意外だった… Prismaでは「前のメジャーバージョンの正しい書き方」を書き、Drizzleでは型とAPIシグネチャでつまずき、生SQLではそのドライバにどんな制約があるのかまったく分かっていない。 ここでは、schemaからマイグレーションまでのこの一連の流れで保険として使っているいくつかのやり方をメモしておく。完全に備忘録だ。 Prisma 7 / Drizzle 0.45 / Next 16。バージョンが合っていないと、以下の書き方はすべて変わるよ。 Prisma 7 でAIが必ず書き間違える箇所がいくつかある acg-site そのリポジトリの CLAUDE.md:18-25 には「重要なバージョン差異(AIのよくある間違い)」という名の節があって、6つのbulletのうちPrisma関連は1つだけ: Prisma 7 の generator は "p...
私のCLAUDE.mdはたった1行:規約ファイルには一体何を書くべきか
最近、Galgame リソースサイトを Hugo から Next.js にまるごと書き直した。リポジトリは vns-next だ。create-next-app の最初の commit からオンラインに載せられるようになるまで、全部で 13 commit、期間は 20 時間足らず。 後からリポジトリを見返していて、なかなか笑えることに気づいた——Claude 用に書いたあの約定ファイル、全文たった 1 行しかない。しかもその 1 行、僕が書いたんじゃないんだよね… この記事はレンダリングモードの話でもデプロイの話でもなく、「プロジェクト立ち上げ時に AI にどんな約定を残すべきか」という一点だけ。いくつかのプロジェクトで試行錯誤してたどり着いた結論を記録しておく… 僕の CLAUDE.md は全文 1 行だけ まず原文から。vns-next ルートの CLAUDE.md、全文はこう: @AGENTS.md そう、この 1 行だけ。import 構文で隣の AGENTS.md を指している。じゃあ AGENTS.md はというと、全文 5 行: <!-- BEGIN:nex...
