Lalabase
티켓·문서·근무시간·고객대화를 한 곳에 모아 KI로 검색, 소프트웨어 기업 고객 프로젝트용, 오픈소스로 자체 서버·독일 클라우드에 배포
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
BATTER BOX — AI課題診断×実行伴走人材プラットフォーム(v1.4) BeFree_技術構成設計.md の「フェーズ1」「フェーズ2」「フェーズ3」に対応する実装です。UIロジックはプロトタイプ(Reactアーティファクト版)をほぼそのまま踏襲し、AI呼び出し・マッチング計算・データ永続化をすべてバックエンド側(Next.jsのAPI Routes + PostgreSQL)に移しています。ブラウザから api.anthropic.com を直接叩くことはありません。 v0.2では、企業/実務経験者/運営の3種類のアカウント認証、両者間のダイレクトメッセージ、人材の稼働上限を反映した足切りフィルター、利用規約・プライバシーポリシーのドラフトを追加しました。v0.3ではランディングページ・人材審査フロー・決済導線の土台・営業資料を追加し、v0.4で企業⇄運営⇄人材の2段階契約構造への変更、v0.5でデザイン全体の刷新(レスポンシブ対応・配色/フォント変更)を行い、v0.9〜v1.3で診断精度の向上・データ活用によるマッチング学習・実運用不具合の修正を重ねました。v1.4でサービスブランドを「BATTER BOX」へ変更しています。README 전문 보기 ↗
BeFree_技術構成設計.md の「フェーズ1」「フェーズ2」「フェーズ3」に対応する実装です。UIロジックはプロトタイプ(Reactアーティファクト版)をほぼそのまま踏襲し、AI呼び出し・マッチング計算・データ永続化をすべてバックエンド側(Next.jsのAPI Routes + PostgreSQL)に移しています。ブラウザから api.anthropic.com を直接叩くことはありません。
v0.2では、企業/実務経験者/運営の3種類のアカウント認証、両者間のダイレクトメッセージ、人材の稼働上限を反映した足切りフィルター、利用規約・プライバシーポリシーのドラフトを追加しました。v0.3ではランディングページ・人材審査フロー・決済導線の土台・営業資料を追加し、v0.4で企業⇄運営⇄人材の2段階契約構造への変更、v0.5でデザイン全体の刷新(レスポンシブ対応・配色/フォント変更)を行い、v0.9〜v1.3で診断精度の向上・データ活用によるマッチング学習・実運用不具合の修正を重ねました。v1.4でサービスブランドを「BATTER BOX」へ変更しています。
サービスブランド名を「BeFree」から「BATTER BOX」に変更しました。あわせて、アップロードいただいたロゴ画像から抽出した実際の色(ネイビー #04162D ×オレンジ #F46919)に、サイト全体の配色を統一しています。
public/logo.png に配置し、ランディングページ・アプリ画面のヘッダーで表示lib/theme.jsxのCOLORSを全面更新(キー名teal/amberは互換のため維持、値はネイビー/オレンジ系に変更)。ボタン・レーダーチャート・実績数値の帯など、サイト全体に反映済み/company(会社概要)ページでは、**サービス名のみ「BATTER BOX」**に変更し、運営会社名(株式会社BeFree、実際の登記情報)はそのまま残しています。利用規約も「当社(株式会社BeFree)が提供する『BATTER BOX』」という書き方に整理しました。サービスブランドと運営法人は別物として扱うのが実態に合うという判断です。もし運営会社自体も改称する場合は、/companyページと利用規約の該当箇所を別途更新してくださいBATTER BOX ADMIN)のみ変更し、配色は従来通り実務ツール的なダークテーマのまま維持しています(社内利用前提のため、意図的に変更していません)人材・企業のデータを一括でダウンロードできるようにしました。管理画面のナビに「エクスポート」を追加しています。
これまでは一覧の表示項目をそのまま出していただけだったので、10軸のスコア/配点を1軸1列で出すようにしました。 そのままピボットテーブルや分析に使えます。
| 列数 | 追加した主な項目 | |
|---|---|---|
| 人材 | 27列 | 10軸の配点(合計100点)、強みの上位3軸、経験機能領域、具体的な経験業務、得意な働き方、価値観、職務経歴 |
| 企業 | 22列 | 10軸のスコア(低いほど深刻)、最優先ボトルネック、登録日 |
アカウント作成だけ・入力途中の方も行として出力され、「登録状況」列で見分けられます(AI対話が未完了の方は軸の列が空欄になります)。
一覧ページはv6.13でメールアドレスでも検索できるようにしましたが、CSVダウンロード側は氏名・会社名しか見ていませんでした。
メールアドレスで検索した状態でダウンロードすると、画面に出ている結果と中身がずれます。
検索条件を lib/adminExport.js の1箇所にまとめ、一覧とCSVで同じものを使うようにしました。
外部パッケージを増やさない方針に合わせ、ZIPの書き出しも自前です(lib/zip.js)。
Node組み込みの zlib でdeflateし、CRC-32とZIPのヘッダ構造は仕様どおりに組み立てています。
対応範囲は「ファイルのみ・UTF-8のファイル名・ZIP64なし」で、CSVを数本まとめる用途には十分です。
tests/csvExport.test.js(9件)と tests/zip.test.js(6件)を追加。テスト全体204件すべて成功
unzip -t コマンドで展開できることまでテストに入れていますzipfileとブラウザの両方で問題なく開けることv6.13で人材側にやったことを、企業側にも同じ形で入れました。
アカウント作成の時点で会社名が空のCompanyレコードを作ります。
会社情報を入力すれば、saveCompanyProfile() が同じレコードを会社名入りで更新します(レコードは増えません)。
企業側の登録も、これで3段階すべてが管理画面から見えます。
| 段階 | 登録状況 | 分かること |
|---|---|---|
| アカウント作成だけ | アカウントのみ | メールアドレス |
| 会社情報の入力まで(AI課題診断が未完了) | 入力途中 | 会社名・業種・フェーズなど |
| AI課題診断まで完了 | 完了 | スキルマップ(マッチング対象になる) |
企業一覧・企業詳細・CSV・ダッシュボードの見え方は人材側と揃えました(メールアドレス列、
メールアドレスでの検索、会社名が空の行は (会社名未入力) 表示、スキルマップが無い場合の黄色い帯)。
会社名が空だと削除の確認文字列も空になって削除できないレコードになる問題も、人材側と同じく表示名で照合して解消しています。
これで /api/diagnosis/start と /api/talent/dialogue/start は、どちらも共通ヘルパー
(lib/companyProfile.js / lib/talentProfile.js)を呼ぶだけになりました。
アカウント作成画面に「途中で中断しても続きから再開できます」と書いてあるのに、 入り直すとフォームが空でした。v6.12〜v6.13で保存はされるようになったので、それを画面に戻します。
/api/me/profile が、AI対話が未完了でも入力済みの内容(companyForm / talentForm)を返すようにしました
(hasData は「スキルマップがあるか」の意味のままなので、他の画面の出し分けは変わりません)/diagnose・/join・/app の3経路すべてで、入力フォームの初期値に反映します保存済みのレコードは項目が欠けていたり、管理画面で書き換えられていたりします。
そのまま <select> の値に渡すと、どの選択肢にも一致せず未選択の空のプルダウンになります。
lib/formRestore.js に切り出して対処しました。
null/undefined の項目は既定値のままにするtests/companyProfile.test.js(7件)と tests/formRestore.test.js(4件)を追加。テスト全体189件すべて成功/diagnose に入力 → 離脱 → 入り直しで会社名・業種・従業員数・成長段階が戻る/join に入力 → 離脱 → 入り直しで氏名・役職・経験年数・業種が戻る/app からも、企業・人材それぞれ同じく戻る/api/talent/analyze と StepTalentAnalyzing は対話フローに置き換わって以降どこからも呼ばれていませんv6.12で「経歴入力(お名前)を送信した時点」での登録までは前倒ししましたが、 アカウント作成(メールアドレス+パスワード)だけで経歴入力に進まなかった方は、 まだ「ユーザーアカウント」ページにしか現れませんでした。人材一覧には出ないままです。
アカウント作成の時点で、氏名が空のTalentレコードを作るようにしました(実務経験者のみ。企業アカウントでは作りません)。
経歴入力まで進めば、v6.12で入れた saveTalentProfile() が同じレコードを氏名入りで更新します。レコードは増えません。
人材側の登録は、これで3段階すべてが管理画面から見えます。
| 段階 | 登録状況 | 分かること |
|---|---|---|
| アカウント作成だけ | アカウントのみ | メールアドレス |
| 経歴入力まで(AI対話が未完了) | 入力途中 | 氏名・役職・経験年数など |
| AI対話まで完了 | 完了 | スキルマップ(マッチング対象になる) |
セッション発行と並行して走らせているので、アカウント作成の待ち時間は増えていません。 保存に失敗してもアカウント作成自体は成立します(経歴入力の送信時に作り直されます)。
アカウントのみ / 入力途中 / 完了 の3段階。氏名が空の行は (氏名未入力) と表示します一覧の削除ボタンは、DB上の氏名と確認文字列を突き合わせて削除を許可しています(v6.6)。
氏名が空だと確認文字列も空になり、サーバー側の if (!confirmText) return で弾かれて
削除できないレコードになってしまうため、画面と同じ (氏名未入力) で照合するよう揃えました。
実際にブラウザから削除できることを確認しています。
v6.12と同じく、スキルマップが無い方は候補一覧・マッチング計算から除外されます。確認済みです。
tests/talentProfile.test.js に3件追加(氏名が空のレコードの生成、表示名のフォールバック、
アカウント作成→経歴入力で同じレコードが更新されること)。テスト全体179件すべて成功/api/me/profile が従来どおり経歴入力へ誘導すること人材側は、アカウント作成 → 経歴入力 → AI対話(5問) → スキルマップ、という順で進みます。 これまで Talentレコードは5問の対話がすべて終わって初めて作られていました。
そのため、経歴を入力したあと対話の途中で離脱した方は、運営側から見るとどこにも存在しませんでした。 管理画面の「ユーザーアカウント」にはメールアドレスだけが残るものの、人材一覧には一切現れないので、 「登録してくれたのか、そもそも届いていないのか」が分かりませんでした。
Talentレコードを作るタイミングを、経歴入力(お名前を入れた画面)の送信直後に前倒ししました。
/api/talent/dialogue/start で、1問目の生成と同時に保存します(直列に積まないので対話の表示は遅くなりません)。
| v6.11まで | v6.12 | |
|---|---|---|
| Talentレコードの作成 | 5問の対話が完了した時点 | 経歴入力を送信した時点 |
| 途中離脱した方 | 人材一覧に出ない | 「入力途中」として出る |
| 稼働枠(Capacity) | 対話完了時に作成 | レコード作成と同時 |
対話が完了したときは、同じ saveTalentProfile() で同じレコードを更新します。
再診断や、途中でやり直した場合もレコードは増えません(作成/更新の出し分けは lib/talentProfile.js に1箇所だけ置いています)。
なお、AIの呼び出しが失敗しても保存は実行されます。保存とAI生成を並行で走らせているため、 APIキー切れやタイムアウトで対話が始まらなかった場合でも、その方が登録しようとした事実は残ります。
一覧に入力途中の方が並ぶようになるので、見分けられるようにしました。企業側も同じ形に揃えています (企業のCompanyレコードは以前から診断開始時に作られていたため、同じ状態が起きていました)。
完了 / 入力途中)。一覧上部に「うち◯名はAI対話が未完了(入力途中)です」スキルマップが無い方は、もともと候補一覧・マッチング計算から除外されています
(skillMaps.length > 0 での絞り込み)。入力途中の方が企業に提案されることはありません。実際に確認済みです。
tests/talentProfile.test.js を追加(フォーム→DB形式の変換、未入力時にundefinedを渡さないこと、作成/更新の出し分け)。テスト全体176件すべて成功approvedのため)。
マッチングには出ないので実害はありませんが、運用上まぎらわしい場合は初期値を見直す余地があります/api/talent/analyze と StepTalentAnalyzing は対話フローに置き換わって以降どこからも呼ばれていません「得意な領域は何ですか」という質問が、経験2年目の自分には難しく感じた。選択肢を見てもどれが当てはまるか悩んだ
というご意見をいただいて修正しました。この質問文はコード内に固定で書かれているものではなく、AIがその場で作っています。原因は2つありました。
プロンプトには「実務経験年数」を入れていましたが、それに合わせて聞き方を変えろという指示がありませんでした。ペルソナ(タクト)はベテランコンサルタントとして設定されているので、相手が誰でも同じ目線で聞いてしまいます。
lib/talentDialoguePrompts.js に experienceGuidance() を追加し、「3年未満」「3〜5年」の方には次を禁止しました。
代わりに、事実をそのまま答えられる聞き方を指示しています。
| 変更前に出がちだった質問 | 変更後に出る質問 | |
|---|---|---|
| 聞き方 | 得意な領域は何ですか? | 直近3ヶ月で、いちばん時間を使っていた仕事はどれに近いですか? |
| 求めるもの | 自分の強みの分類 | 事実の報告 |
経験5年以上の方への聞き方は変えていません(役割や裁量に見合った聞き方のままです)。
対話UIは選択肢ボタンだけで、自由入力がありません。当てはまるものがないと、近いものを無理に選ぶしかなく、その答えがそのままスキルマップの採点に使われていました。
選択肢の下に「この中に当てはまるものがない」を常設しました(lib/dialogueAnswers.js に文言を1箇所だけ定義し、UIとプロンプトの両方から参照)。
経験年数が未入力のままプロンプトを組むと「実務経験年数: undefined」とAIに渡っていました(lib/talentDialoguePrompts.js と lib/talentPrompts.js)。「未入力」と出るようにしています。
tests/dialogueEscape.test.js を追加(8つのプロンプト生成すべてに逃げ道の扱いが入ること、経験年数による出し分け、undefined混入がないこと)。テスト全体は171件すべて成功管理画面で企業の詳細を開くと必ず500エラーになっていました。
app/admin/companies/[id]/page.jsx の171行目で、存在しない変数 companyId を参照していました。
- <input type="hidden" name="redirectPath" value={`/admin/companies/${companyId}`} />
+ <input type="hidden" name="redirectPath" value={`/admin/companies/${company.id}`} />
companyId はこのファイル内の別コンポーネント RecordedMatches の引数で、
ページ本体のスコープには存在しません。ページ本体では company.id が正しい名前で、
同じファイルの他の4箇所はすべて company.id を使っていました。
この行は10軸のスコアを手動調整するフォームの中にあり、全10軸ぶん描画されるため、 スキルマップを持つ企業の詳細ページは例外なく開けませんでした (人材側の詳細ページは同じ構造ですが正しく書かれており、問題ありません)。
いつから: 2026-08-17の初回アップロード(5f0bbab)から入っていた既存のバグです。
v6.9の高速化で持ち込んだものではありません。
npm run lint が初期設定のプロンプトを出すだけの状態でした。
未定義変数は no-undef で機械的に検出できる種類のバグです同じ種類のバグが他にないか、管理画面を全ページ巡回して確認しました。
redirectPath が壊れていた箇所です)未対応: ESLintの設定は入れていません。next build はESLint設定があると
ビルド時にlintを実行するため、既存の警告が大量に出てデプロイが止まる可能性があります。
入れるなら、まず警告を洗い出してから別途対応するのが安全です。
実際にデータを入れて計測してから直しました。推測で触った箇所はありません。
300社・300人・契約60件を入れて全ページの応答時間とHTMLの大きさを測ったところ、 遅かったのは1ページだけでした。
| 修正前 | 修正後 | |
|---|---|---|
/admin/matches | 926ms / 575KB | 50ms / 54KB |
/admin/engagements | 58ms / 533KB | 29ms / 190KB |
/admin/projects | 34ms / 93KB | 23ms / 42KB |
| 他のページ | 20〜85ms | 変化なし(元から速い) |
原因は2つあり、どちらもDBではありませんでした(DBは全体で35ms・クエリ4本)。
(a) 同じ計算を18万回やり直していた
scoreMatch() は「企業×人材」の総当たりで呼ばれますが、中身を分解すると
のに、1回の呼び出しごとに両方ゼロから計算し直していました。300×300の表では
同じ正規化を18万回繰り返していたことになります。
スコアの元オブジェクトをキーにしたWeakMapで中間結果を使い回すようにして、
CPU 744ms → 76ms(約10倍)。呼び出し側のコードは一切変えていません。
この関数は候補一覧(/api/match/company)でも使われているので、企業側の画面も速くなります。
スコアの値は1点も変わっていません。旧実装をそのまま写したものと、 ランダムな入力2万件+同一オブジェクトを使い回すケースで突き合わせ、不一致0件を確認しました。
(b) 300社を1ページに全部描いていた
企業・人材一覧は20件ずつに区切られていましたが、マッチング・契約・プロジェクトは未対応でした。
同じ20件区切りに揃え、ページ送りは共通部品(AdminPager)に出しました。
マッチング表は「企業×人材」なので、区切ることで1ページあたりの計算量も頭打ちになります。
あわせて、読む必要のない列を読んでいた箇所を絞りました(マッチング表の人材の経歴文、 インサイトの企業レコード全体など)。
ラリー1問ごとにDBの往復がAI呼び出しと直列に積まれていた
本番のDBはネットワーク越し(Neon)なので、1往復ごとの遅延がそのまま体感に乗ります。 互いに独立な処理を並行に変えました。
質問が1文字ずつ出るようにした(ストリーミング)
これまでは全文ができるまで「入力中…」で待たせていました。生成の途中経過をそのまま流し、 チャットのように文字が伸びていく形にしています。実時間は変わりませんが、 「止まっている」感じがなくなります。
応答はJSONのまま(選択肢や軸キーをそのまま使うため)で、 出来かけのJSONから表示する文字列だけを取り出して流しています。
最終採点を高速モデルに変更
診断の最後の1回だけ高精度モデル(Sonnet 5)で日本語1,000〜1,400字を生成しており、 ここが結果表示直前の最大の待ちでした。人材側の最終採点は既に高速モデル (「モバイル中心なので速度優先」とコードに明記)だったので、企業側も揃えました。
この変更だけは10軸スコアの精度に影響する可能性があります。マッチングの根幹なので、
実データで結果を見て、精度が落ちたと感じたら app/api/diagnosis/answer/route.js の
最終採点の { fast: true } を外せば元に戻ります。
この環境には ANTHROPIC_API_KEY が無く、AI呼び出しの実測はできません。
代わりに、壊れると質問が出なくなる部分をテストで固めました(25件追加、合計164件)。
\uエスケープの途中切れ、似た名前のキー、表示が巻き戻らないことfetchを差し替えて、累積本文・```json フェンス・
max_tokens打ち切り・途中のerrorイベント・JSON破損そのうえで、ブラウザで実際に対話画面を開き(1問目のSSEを差し替え)、 質問と振り返りが表示され、選択肢が押せ、途中の文字列が残らず、重複もしないことを確認しました。
未検証: 本物のAnthropic APIとの接続部分だけは動かせていません。デプロイ後に一度、 診断を通しで実行して確認してください。
「全部入力し終えたあとに登録を求められるのが面倒がられる」というご指摘への対応です。 登録を一番最初に持ってきて、ログイン済みの状態で入力してもらう形に変えました。
| v6.7まで | v6.8 | |
|---|---|---|
企業 /diagnose | 会社情報 → AI課題診断 → 診断結果 → アカウント作成 | アカウント作成 → 会社情報 → AI課題診断 → 診断結果 |
人材 /join | 経歴入力 → AI自己分析 → スキルマップ → アカウント作成 | アカウント作成 → 経歴入力 → AI自己分析 → スキルマップ |
最初の画面はメールアドレスとパスワードだけです。登録した時点でログイン状態になるので、 以降の入力内容と診断結果は各APIがその場で保存します。途中で離脱しても、次にログインすれば 続きから再開できます(これまでは最後の登録に到達しないと、入力も診断結果もすべて失われていました)。
登録時に、ワンタイムのログインリンク付きメールを送ります。PCで登録してスマホで続きをやる場合や、 間が空いてパスワードを忘れた場合でも、メールのリンクから同じアカウントに戻れます。
/continue 画面のボタン(POST)で初めて消費しますメール認証を必須の関門にはしていません。 登録したらその場で入力に進めます。 入口でメールの往復を強制すると、スマホではメーラーとの行き来が発生して、 今回解消しようとしている「面倒」を入口に移すだけになるためです。
/api/diagnosis/claim と /api/talent/claim を削除。「未ログインで進めた結果を、最後に
アカウントへ引き取る」ための口でしたが、常にログイン済みで入力する新しい順序では
呼び出し元が無くなります。v5.1で評価UIが呼び出し元を失って到達不能になった件と同じ形なので、
放置せず削除しましたProgressRail に minClickableStep を追加/diagnose を開くと、
エラーではなく「企業アカウントでログインしてください」と出すようにしましたPasswordResetToken を消していなかったため、
パスワード再設定を一度でも使った利用者は管理画面から削除できない状態でした(今回追加した
LoginToken とあわせて削除対象に追加)LoginToken テーブルを追加しました。ビルド時の prisma db push で自動的に反映されます
(追加のみなので既存データへの影響はありません)。
ローカルにPostgreSQLを立て、実際にブラウザで登録フローを通して確認しました。
/diagnose・/join ともに1画面目がアカウント作成になり、登録後そのまま次のステップへ進むLoginToken が7日後の期限で発行される/continue を開いただけではログインしない/ボタンで初めてログインする/2回目は
「期限切れか、すでに使用済み」/不正トークン・token無しもそれぞれ正しく弾く/app を開くと、入力の続き(会社情報)から再開する未検証: AI対話そのもの(ステップ2〜3)は、この環境に ANTHROPIC_API_KEY が無いため
動かしていません。該当APIは認証まわりを変更していないため影響はない想定ですが、
デプロイ後に一度通しで確認してください。
企業アカウントで実際にログインし、PC(1280px)とiPhone 16(393px)で全画面を歩いて計測しました。 見つかった13項目のうち12項目を修正しています(残り1項目は後述)。
① ブラウザの戻るでログイン画面まで飛ばされていた
表示中の画面がReactのstateだけで管理され、URLは常に /app のままでした。そのため、
?view=... をURLに同期し、popstate(戻る/進む)で画面を復元するようにしました。
検証: お支払い→設定→ダッシュボードと進んで「戻る」→設定に戻る / 設定でリロード→設定のまま。
診断ウィザードと個別スレッドは、途中のstep・開いているスレッドまでURLに持たせていないため 復元対象から外しています(スレッドはメッセージ一覧に寄せます)。
② 「人材を探す」がナビに無かった
企業の主目的である候補一覧は、**マイページを一番下までスクロールして「最適な人材を見る」**を
押すしか行き方がありませんでした。ナビに「人材を探す」を追加し、1クリックで開けるようにしています。
?view=candidates で直接開くこともできます。
③ 候補一覧で「すでに連絡した人か」が分からなかった
同じ人に二重に連絡してしまう状態でした。契約中 / やり取り中 / 連絡済み のバッジを
候補カードに出します(/api/match/company が relation を返すようにしました)。
| 項目 | 修正前 | 修正後 |
|---|---|---|
淡いグレーの文字(COLORS.faint) | 白地で 2.19:1 | 5.05:1(#A6B0C0 → #646F85) |
| 「進行中」バッジ(白文字×オレンジ) | 3.05:1 | 4.57:1(濃いオレンジへ) |
| 「入金済み」バッジ(白文字×緑) | 3.45:1 | 5.38:1 |
| 顔写真がない人のイニシャル | 1.12:1(ほぼ見えない) | 11.46:1 |
| 未読バッジの赤 | 3.91:1 | 5.45:1 |
| ステッパーの丸数字 | 3.05:1 / 2.89:1 | 4.3:1以上 |
| レーダーの目盛り | 9px | 11px |
| 下部タブのラベル | 9〜10px・「ダッシュボ…」と省略 | 11px・省略なし(折り返し) |
| 10〜10.5pxの文字 | 14箇所 | すべて11pxへ |
| 44px未満のタップ領域 | モバイル7件 | 0件(.btn-ghostに44pxの下限) |
| ヘッダーのナビ | 幅が足りず2行に折り返して崩れる | 1行固定(white-space: nowrap + 上限880→1080px) |
ナビの折り返しは、.top-nav のボタンがflexの既定(flex-shrink: 1)で縮むのが原因でした。
フォントの読み込みに失敗した環境で実際に崩れます。
22/100点 の表記を追加プライマリボタン(「保存する」「変更する」など)の白文字×ブランドオレンジが 3.05:1 です
(14.5px太字なので基準は4.5:1)。濃い方のオレンジ #C35414 にすれば 4.57:1 を満たしますが、
サイト全体のボタンの色味が一段落ち着くため、ブランドの見え方に関わる判断として残しています。
ご希望があれば切り替えます。
PC(1280px)とiPhone 16(393px)で、マイページ・ダッシュボード・人材を探す・プロジェクト・ お支払い・設定・メッセージを実際にクリックして回り、画面ごとに自動計測しました。
企業一覧(/admin/companies)と人材一覧(/admin/talents)の右端に「削除」列を追加しました。
lib/adminDelete.js のカスケード削除。スキルマップ・マッチング・契約・プロジェクト・請求書・メッセージ・ログインアカウントを子から順に1トランザクションで削除)/admin/audit-log)にも従来どおり記録されますあわせて、削除の確認処理の穴を塞ぎました。これまでサーバー側は expectedName と confirmText を比較していましたが、どちらもクライアントから送られてくる値だったため、一致チェックとして機能していませんでした。DB上の会社名・氏名と突き合わせるように変更しています(画面を開いたあとに改名された場合もここで弾けます)。
詳細ページ側の「名前を手入力して削除」はそのまま残しています。
何が問題だったか
人材スコアは10軸それぞれを0〜30点で独立に採点していました。経験豊富な人ほど全軸が高くなるため、どの企業から見ても同じ人が上位に並びます。これではハイレイヤーの人材が登録されるたびに、ローレイヤーの人材に打席が回らなくなります。
どう変えたか
| v6.5まで | v6.6 | |
|---|---|---|
| 人材スコア | 各軸0〜30点を独立に採点(合計は0〜300点でバラバラ) | 経験100点を10軸に配分(合計は誰でも必ず100点) |
| 企業スコア | 各軸0〜100点(低いほど深刻) | 変更なし(総合スコア・Growth Mapはそのまま) |
| 企業の課題の重み | その場で gap を計算 | 課題100点の配分として明示的に算出(issueAllocation) |
| 強さの判定 | スコア / 30 | 配点 / 30(30点集まれば「この軸は即戦力」) |
合計100点なので、30点以上を取れる軸は最大3つです。つまり誰であっても「すべての企業に対して満点」にはなりません。満点が出るのは、その企業の課題TOP3にちょうど経験が寄っている人だけです。
実際の効き方(シードデータ + 仮想の人材で実測):
■ 株式会社ハレノ 課題配点TOP3: leadership 15点 / ops 13点 / hr 11点
71% 宮崎 大輔(人事・組織の専門家)
50% 現場たたき上げ(店長)
39% 万能ハイレイヤー(旧式なら全軸満点だった人)
36% 小池 美咲(財務の専門家)
21% 遠藤 慧(営業の専門家) ← 足切り
■ 合同会社ミライバ 課題配点TOP3: ops 13点 / finance_mgmt 11点 / tech 11点
50% 小池 美咲
50% 現場たたき上げ(店長)
39% 万能ハイレイヤー
34% 宮崎 大輔
20% 遠藤 慧 ← 足切り
移行作業は不要です
normalizeAllocation() はべき等(正規化済みの値を入れても変わらない)なので、配点制より前に保存された0〜30点のスキルマップも、同じ式にそのまま通せます。合計で割って100点に引き直すため、強みの偏り(形)はそのまま保たれます。DBの書き換えは行っていません。
軸の深掘りも「取り分の付け替え」になりました
1つの軸だけを上げることはできないので、深掘りの結果をその軸に当てはめたうえで10軸全体を100点に引き直します。ある軸を厚くすると、他の軸はその分薄くなります(/api/talent/axis-deep-dive が10軸すべての新しい配分を返します)。
内訳表示も本体と同じ式に揃えました
マッチ内訳の「課題領域一致」が MATCH% 本体と別の式だったため、「MATCH 54%」なのに内訳は11/30、という食い違いが出ていました。本体と同じ「課題TOP3の加重平均」に揃えています(54% → 14/30)。
主な変更ファイル: lib/axes.js(normalizeAllocation / issueAllocation / TALENT_FOCUS_POINTS)、lib/matching.js、lib/matchBreakdown.js、lib/talentPrompts.js、lib/talentDialoguePrompts.js、app/api/talent/*
ローカルにPostgreSQLを立て、シードデータを入れて実際にログインして確認しました。
/30 表記が残っていないこと、レーダー・バー・強み3軸がすべて配点で描画されることを確認/api/match/company が候補とその内訳を返すことを確認人材登録(/join の最初のステップ)の「直近の役職」プルダウンに、現場を預かる役職を追加しました。
店舗・現場クラス
店長 / 副店長 / チーフ / サブチーフ / 現場責任者(現場監督)
定義場所: app/app/page.jsx の TALENT_TITLE_GROUPS
(/join は StepTalentInput を再利用しているため、ここ1か所で両方に反映されます)
未対応: 設定画面(/app のプロフィール)の「直近の役職」は、登録時と違って自由入力のままです。
登録後に選び直したい場合はここを同じプルダウンに揃えたほうが良いので、ご希望があれば対応します。
PC(1280px)とiPhone 16(393px)で /join を描画して確認しました。
店長 で確定、「その他」用の自由入力欄は出ないご指摘いただいた4点の対応です。
public/lp/side-company.jpg / side-talent.jpg を、いただいた画像の情景に差し替えました。
添付いただいた画像はすでに文字が焼き込まれたセクションのモックアップでした。 そのまま敷くとページ側の文字と二重になるため、左側の文字組みを切り落とし、写真部分だけを 背景に使っています(文字はこれまで通りHTMLで重ねているので、後から文言を変えられます)。
objectPosition)
62% 42% … 窓の向こうの夕陽と街並みを残す64% 46% … 打席の白線とホームベースを残すletter-spacing: -0.03em)max-content にして、2列が間延びしないように44pxは残しました: 指で押せる大きさの下限です。ここをさらに詰めると、 「スマホでのタップのしやすさは落とさないで」というご要望に反するためです。 代わりにモバイルでは文字を13px→14pxに上げ、行の中の空きが目立たないようにしています。
FinalCTA セクションを削除し、TWO SIDESでページが終わる構成にしました。
「その経験に、次の打席を。」というコピー自体は、TWO SIDESの人材側
「あなたの経験に、次の打席を。」として残っています。
会員登録への導線は、ヘッダーの「無料で始める」・ヒーロー・モバイル固定CTAで確保しています。
原因: 2つの面に分け、それぞれ min-height: 380px を確保していたため、
38文字に約890pxを使っていました。同じ大きさの文字が2回続くので、視線が動かず間延びします。
対応: 1つの面にまとめ、主文と副文でサイズ差をつけました。
| v6.3 | v6.4 | |
|---|---|---|
| 構成 | 同じ大きさの面が2つ | 1面・左右非対称 |
| 「経験を、眠らせない。」 | display(最大94px) | 主文 最大70px |
| 「その経験を、必要としている会社がある。」 | display(最大94px) | 副文 最大27px・縦罫つき |
| 高さ(PC) | 約890px | 341px |
| 高さ(モバイル) | 約445px | 291px |
モバイルで、ヒーローのGrowth Map(レーダーチャート)の軸ラベルが実寸8.3pxでした。
SVGはviewBoxごと縮むため、font-size: 9.5 と書いていても縮小率(約0.88)がかかり、
自分で決めた下限11pxを割っていました。ユーザー座標を12.6pxに上げて実寸11.0pxに修正。
あわせて、左端に来る「オペレーション」は枠からはみ出すため「業務・運用」に短縮しています。
PC(1440px)とiPhone 16(393px)の実機相当で描画して確認しました。
「オレンジのブランド面」と「TWO SIDES」が空白に見える、というご指摘への対応です。 背景画像を入れる想定ではなく、意図的にタイポグラフィだけの面として作っていましたが、 実測すると狙った「大胆な余白」ではなく、ただの空白になっていました。
| 高さ | 中身 | 空白 | |
|---|---|---|---|
| TWO SIDES 各側(PC) | 702px | 271px | 431px(61%) |
| ブランド面(オレンジ) | 940px | 38文字 | — |
いただいた写真が**「左=企業チーム / 中央=AIのハブ / 右=実務経験者」**という構図だったため、 左右に割って、それぞれのサイドの背景にそのまま使いました。装飾ではなく、意味が一致しています。
左 FOR COMPANIES ← 写真の左側(PCを覗き込む企業のチーム)
右 FOR PROFESSIONALS ← 写真の右側(話している実務経験者)
白文字の可読性を確保するため、下に行くほど濃くなるスクリムを重ねています (文字が乗る下半分を最大97%まで濃く)
各サイドに補足の1行を追加(「5問・約3分の診断から。結果を見るまで無料です。」等)
ホバーすると写真がわずかに拡大し、スクリムが薄くなる(hover: hover の環境のみ)
高さを702px → 540px(モバイルは320px)に調整
こちらは意図的に写真を入れない判断をしました。理由は2つです。
代わりに、ブランドの造形(ホームベースの五角形)を巨大に、ごく薄く(白9%)重ねて画面を持たせました。 あわせて高さを940px → 890px、ブロックの余白を最大104px → 76pxに詰めています。
写真とテキストを足しても、モバイルの可読性は維持されています。
| 結果 | |
|---|---|
| iPhone 16の孤立行 | 0件 |
| 11px未満の文字 / タップ44px未満 | 0件 / 0件 |
| 横方向のはみ出し | 0px(1440 / 393) |
| コンソールエラー | 0件 |
| 自動テスト | 130件パス |
sharp を一時導入し、作業後にアンインストールしていますpublic/lp/side-company.jpg / side-talent.jpg)「採用するほどじゃない。/ でも、その経験が必要だ。」「マッチングして、/ 終わりではありません。」が、 iPhone 16(幅393px)で最後の1文字だけ次の行に落ちていました。
原因は lp-giant の下限34pxが、iPhoneの本文幅(393 − 左右余白40 = 353px)に対して大きすぎたことです。
最も長い行「でも、その経験が必要だ。」は12文字なので、1文字あたり29px以下でないと折り返します。
lp-giant: clamp(34px, 6.2vw, 88px) → clamp(26px, 5.6vw, 74px)lp-display: clamp(40px, 7.0vw, 100px) → clamp(34px, 6.6vw, 94px)デスクトップでも上限を88→74px / 100→94pxに下げているため、ご要望どおり全体に少し小さくなります。 あわせて、オレンジのブランドセクションの「必要としている会社がある。」(13文字)も、モバイルで 折り返していたため3行に割りました。
検証: iPhone 16の幅で全見出しの各行の文字数をRange APIで実測し、 最終行が2文字以下になる「孤立行」が0件であることを確認しています。
見づらいとのご指摘により、Factsセクションを削除しました(app/_lp/Content.jsx から Facts も除去)。
「企業の方 〜 お問い合わせ」の7リンクが横1列に流れていたため、
grid-template-columns: repeat(4, max-content) でデスクトップ2行に整理しました
(モバイルは2列×4行)。
Heroのラベルと、フッターの同じ文言を削除しました。
いただいた2点を、それぞれ意味の合う位置に配置しました。
| 写真 | 配置 | 理由 |
|---|---|---|
| 考え込む経営者+浮かぶ経験の円 | THE PROBLEM のリード文の右 | 「採用するほどじゃない。でも、その経験が必要だ。」そのものの絵。写真内のオレンジの線がブランドカラーと一致する |
| 企業チーム⇄AI⇄人材チーム | AI EXPERIENCEの直前に全幅バンド | 左に企業・中央にAI分析・右に人材という構図がサービスの全体像そのもの。白い写真から黒いAIセクションへの転換点になる |
<picture> の media で出し分けていますobject-fit: cover で高さを clamp(200px, 30vw, 460px) に制御(モバイルは最大260px)loading="lazy" と width/height を指定し、遅延読み込みとレイアウトシフト防止を行っていますapp/opengraph-image.jsx(動的生成)を削除し、ロゴから作った静的画像 app/opengraph-image.png
(1200×630・65KB)に差し替えました。白背景にロゴを中央配置し、下端にオレンジの帯を入れています
(白いサムネイルでも輪郭が分かるようにするため)。
あわせて app/layout.jsx に metadataBase を設定しました。これがないとOGP画像が相対URLのままになり、
LINEは絶対URLでないと画像を取得できません。lib/siteUrl.js 経由でVercelの実URLに解決されます。
og:image / og:image:width / og:image:height / og:type / og:site_name / og:localetwitter:card = summary_large_image注意: 独自ドメインを設定した場合は、Vercelの環境変数 NEXT_PUBLIC_SITE_URL にそのURLを設定してください。
未設定の場合はVercelが自動設定する VERCEL_URL(デプロイごとに変わるURL)が使われます。
| 結果 | |
|---|---|
| iPhone 16での孤立行 | 0件 |
| 11px未満の文字 | 0件 |
| タップ領域44px未満 | 0件 |
| 横方向のはみ出し | 0px(1440 / 393) |
| コンソールエラー | 0件 |
| 実測フォントサイズ(モバイル) | 本文15px / リード16.5px / 補助13px / ラベル11.5px / 入力欄16px |
| フッターのリンク | デスクトップ2行 / モバイル4行 |
| 自動テスト | 130件パス |
sharp を一時的に導入して使い、作業後にアンインストールしています
(生成済みの画像ファイルだけがリポジトリに残ります)。再度画像を差し替える場合は npm i -D sharp してくださいpublic/people/01.jpg 〜 を置いて PEOPLE_PHOTOS_READY を true にすると切り替わります)v6.0のLPは「未完成感がすごい」というフィードバックを受け、原因を実測で特定して修正しました。 素材不足ではなく、作り込み不足が原因でした。
.lp a { color: inherit } の特異度(0-1-1)が .lp-btn--*(0-1-0)より高く、<a>で作った
CTAの文字色をすべて上書きしていました。
| 場所 | 実際の表示 |
|---|---|
| ヘッダー「無料で始める」 | 黒文字 on オレンジ(本来は白) |
| Hero「企業として経験を探す」 | 黒文字 on オレンジ(本来は白) |
| モバイル固定CTA | 黒文字 on オレンジ(本来は白) |
| 最終CTA「BATTER BOXを始める」 | 白文字 on 白=完全に不可視 |
<button>/<span>で作った同じ見た目のボタンは正常だったため、見落としやすい状態でした。
ページで最も見られる要素が全滅しており、これだけで相当の未完成感が出ていました。
全セクションの高さと文字数を実測した結果:
| v6.0 | v6.1 | |
|---|---|---|
| ページ総高 | 11,135px | 12,165px |
| 総文字数 | 1,478文字 | 3,005文字 |
| 1,000pxあたりの文字数 | 133 | 247 |
| ブランドセクション | 1,944px / 50文字 | 940px / 38文字 |
| HOW IT WORKS | 2,931px / 342文字 | 2,389px / 554文字 |
「余白を大きく・情報を詰め込まない」という指示を過剰に適用し、余白の中に入れる中身ごと 削ってしまっていました。デザイン会社のLPは「作り込まれた密度の高い内容のまわりに大きな余白がある」 状態であり、v6.0は余白だけでした。
Heroの右側を、意味の伝わる中身に差し替え
「ON DECK」と書かれた打席の線画は、何を意味するか伝わらず削除しました。代わりに 実際の診断結果のプレビュー(企業成長スコア47/100・10軸レーダー・成長を止めている課題TOP3)を 配置しています。ファーストビューで「診断すると何が出てくるのか」が分かるようになりました。
削ったコンテンツを、新しいデザイン言語で作り直して復帰
<details>/<summary>でJSなしの開閉。左に見出し、右に質問の2カラム過大だった余白の是正
セクション余白を clamp(96px, 13vw, 200px) → clamp(72px, 8.5vw, 132px) に。
HOW IT WORKSの各パネルの min-height: 62svh を撤廃。ブランドセクションは3画面→2画面に縮小し、
決め台詞「その経験に、次の打席を。」は最終CTAとの重複を避けて最後まで取っておく構成に変更。
情報量を増やしても可読性を落とさないよう、下限を定義して実測で検証しました。
| 下限 | 実測 | |
|---|---|---|
| 本文 | 15px | 15px |
| リード文 | — | 16.5px |
| 補助テキスト | 13px | 13px |
| 英字ラベル | 11px | 11px |
| 入力欄 | 16px(iOSのズーム回避) | 16px |
| タップ領域 | 44px以上 | 44px未満: 0件 |
| 11px未満の文字 | 0件 | 0件 |
検証の結果、Growth Mapのラベル・課題の軸名・人物の番号・HOW IT WORKSの英字ラベルなど 8箇所が10〜10.5pxになっており、ロゴのタップ領域が32pxだったため、いずれも修正しています。
height="auto" という不正な属性を指定しており、コンソールエラーが出ていた(CSSで制御するよう変更)ローカルでサーバーを起動し、ブラウザで実測しています。
public/people/01.jpg 〜 を置いて
app/_lp/tokens.js の PEOPLE_PHOTOS_READY を true にすると切り替わります。
「人の温度を感じるか」は写真が入って初めて満たされますLPを「デザイン会社が制作した水準」まで引き上げるため、app/page.jsx を全面的に作り直しました。 アートディレクション・タイポグラフィ・余白・レイアウト・モーション・人物表現・コピーの見せ方まで設計し直しています。
適用範囲はLP(/)のみです。ログイン後のアプリ画面は従来の lib/theme.jsx のまま動きます。
中心思想は EXPERIENCE MOVES BUSINESS. — 企業を動かすのは人員ではなく「経験」。 BATTER BOXは人を紹介する会社ではなく、企業に不足している経験を発見し、流通させるプラットフォームとして表現しています。
ブランドコピーは「会社に足りない経験を、必要な分だけ。」(説明的)から、 「その経験に、次の打席を。」(宣言的)へ変更しました。
最大の差別化要素は、BATTER BOXという名前が持つ「打席」のメタファーがこれまでデザインに一切反映されていなかった点でした。これを共通の造形言語にしています。
HOME_PLATE_CLIP)app/_lp/tokens.js)ページごとに場当たり的なCSSを書かず、Color / Typography / Spacing / Container / Radius / Motion / Breakpoints をすべてトークン化し、app/_lp/LPStyle.jsx の1ファイルに集約しました。
| 内容 | |
|---|---|
| Color | WHITE 60〜70% / ORANGE #F46919 20〜30% / ニアブラック #121214 約10%。グラデーションは使わず単色のベタ面で使用。ネイビーは不使用 |
| Typography | 日本語: Noto Sans JP 900(従来の角丸ポップ体 M PLUS Rounded 1c から変更)/英字: Archivo 800-900 |
| Type scale | display clamp(40-100px) / giant clamp(34-88px) / body 15.5px / label 11px(字間0.16em)。最大と最小で約9倍のジャンプ率 |
| Spacing | セクション余白 clamp(96-200px)、左右 clamp(20-80px)、コンテンツ幅 1440px |
| Radius | 0 / 2 / 4px のみ。角丸を大きくせず、すべてをカード化しない |
| Shadow | 原則不使用 |
スクロールで世界が切り替わる構成にしました。
01 HERO WHITE 左右非対称・巨大コピー
02 MARQUEE ORANGE 横に流れる経験の一覧
03 AI EXPERIENCE ニアブラック 巨大入力UI・疑似体験
04 THE PROBLEM WHITE 文字と余白だけ(カードなし)
05 PEOPLE WHITE 千鳥配置
06 BRAND ORANGE 3段展開のブランド広告
07 HOW IT WORKS WHITE スクロール連動
08 TWO SIDES ニアブラック 左右2分割・ホバーで拡張
09 FINAL CTA ORANGE 巨大コピーのみ
FAQ・料金比較・「マッチング後も並走」はLPから外しました。情報を詰め込むほどブランドは薄まるためです(必要なら下層ページへ)。
BATTER BOX → その経験に、 → 次の打席を。 の順に0.06/0.18/0.32秒で入場。スクロールすると03でAI入力UI(B案)が全面黒で現れるAnalyzing... → 経営課題タグ → NEEDED EXPERIENCE 01/02/03 → MATCHED、の4段で結果が展開します。LP上のデモである旨を画面に明記していますprefers-reduced-motion で停止します現時点で人物写真の素材がないため、写真の位置にブランド造形(ニアブラック/オレンジの面+ホームベースの五角形+経験の見出し)を配置しています。単なるグレーのプレースホルダーではなく、それ自体が成立する造形にしました。
public/people/01.jpg 〜 を置き、app/_lp/tokens.js の PEOPLE_PHOTOS_READY を true にすると写真表示に切り替わります(false の間は404リクエストを出しません)。
「誰なのか」より「何を経験したのか」を主役にする方針で、売上 0→10億 新規事業 3回 IPO 経験 海外 5カ国展開 を最も大きく、氏名・職種はその下に小さく置いています。
ビルドが通っても壊れている箇所があったため、実際にサーバーを起動しブラウザで描画して検証しました。
"use client" モジュールの色オブジェクトを import しており、ページが500エラーで落ちていました(ビルドは成功していた)。色は tokens.js から直接読むよう修正<style>{\...`}の形でCSSを渡すと、Reactが'を' にエスケープするためサーバーとクライアントでテキストが一致せず、毎回コンソールにエラーが出ていました。dangerouslySetInnerHTMLに変更。**同じ欠陥がlib/theme.jsxのGlobalStyle` にもあったため、こちらも修正しています**(見た目は変わらない純粋なバグ修正)paddingTop: 0 がCSSを上書きしていましたline-break: strict を追加検証結果: 横方向のはみ出し 0px(デスクトップ1440・モバイル390の両方)、コンソールエラー 0件。
PEOPLE_PHOTOS_READY を切り替えるだけで反映されます5件のフィードバックへの対応。
/company の掲載内容を確定させました。
| 項目 | 変更後 |
|---|---|
| 所在地 | 〒105-0014 東京都港区芝2-28-11 芝MKビル8F(本社のみ。営業所の記載を削除) |
| 資本金 | 3,000万円(1,510万円から変更) |
| 従業員数 | 正社員35名 / 業務委託15名(正社員48名から変更) |
| 事業内容 | 「Be Sales」の記載を削除し、BATTER BOXの企画・開発・運営のみに |
| 代表者 | 欄ごと削除 |
あわせて、「出典: befree-inc.com」の注記と「(ご記入ください)の箇所は未確定です」の警告を削除し、ロゴ付きの固定ヘッダーを追加しました(ロゴからトップへ戻れます)。
代表者欄の削除に伴うプライバシーポリシーの調整: 個人情報保護法第32条第1項は、法人の代表者名を「本人の知り得る状態」に置くことを求めていますが、同項は**「本人の求めに応じて遅滞なく回答する場合を含む」**としています。会社概要に掲出しない方針に合わせ、プライバシーポリシー第1項の代表者欄を「ご請求に応じて遅滞なく回答します」という記載に変更しました。この請求に確実に応答できる運用が必要です。
ご要望の2点はv0.7〜v4.1の時点で実装済みです。
/admin/inquiries で全件確認でき、「未対応 / 返信済み / 対応完了」のステータス管理もできます。管理画面のナビには未対応件数の赤いバッジも出ますもし「メールが届いていない」状況であれば、原因は**RESEND_API_KEY がVercelの環境変数に設定されていないことである可能性が高いです。未設定の場合、メールは送信されずサーバーのログに出力されるだけ**という仕様になっています(v3.8で意図的にそうしています)。設定手順はREADMEの「メールのために必要な追加設定」の項をご覧ください。
崩れの原因が特定できました。ヒーローが padding-bottom: 220px で巨大な空白を作り、その上に診断プレビューを margin: -160px で右上に重ねるという構造だったためです。横長の画面を前提にした作りなので、スマホでは「巨大な空白 → 右に寄った小さなカード」という見え方になっていました。
.app-topbar を使用。ロゴからトップへ戻れます。狭い画面では副次的なリンク(お問い合わせ等)を隠し、「ログイン」だけに絞りました「クロードで作成した感じ」については、同じ形のカードが同じリズムで並び続けるのが主因だと判断し、次の手を入れています。
HOW IT WORKS / ISSUES / FAQ 等)+見出し+リード文。中央寄せのボールド文字だけ、という状態をやめました.lp-band)と淡いブルーグレーの背景を交互に配置し、セクションの切れ目が分かるようにしましたセールス基盤 のような軸名を出し、左端にアクセント色の縦線を入れました。10軸診断との対応が見えるようになります.lp-section / .lp-h2 / .lp-lead などで一元管理し、画面幅に応じて連動させています(従来はセクションごとに直書きでバラついていました)人材側の下部タブから「ダッシュボード」を削除し、マイページ / メッセージ / プロジェクト / 入金予定 / 設定 の5項目にしました(企業側は従来どおり6項目)。
ダッシュボードの中身(今月の稼働時間・未完了タスク・企業からの評価・推奨案件)が到達不能にならないよう、マイページ上部に「稼働・評価の状況」ボタンを残しています。不要であれば削除します。
アプリ画面・ランディングページ・会社概要・利用規約・プライバシーポリシー・新規登録画面のすべてで、ヘッダーのロゴから / に戻れるようにしました。
min(56vh, 560px) に変更し、画面の大きさに追従するようにしましたhero-illustration.jpg をそのまま使っています。イラスト自体を差し替えたい場合は、public/ に置き換えるだけで反映されます7件のフィードバックへの対応。
原因は明確で、両ダッシュボードとも「全幅のセクションを縦に積むだけ」の構造でした。CSSグリッドを一切使っておらず、marginBottom: 24 のセクションが企業側5つ・人材側7つ並び、さらにリスト(課題TOP3・未完了タスク・進行中案件)に件数上限がなかったため、データが増えるほど縦に伸び続けていました。
カオナビ・タレントパレットのような業務ダッシュボードの構成に寄せて、「指標タイル → パネルのグリッド」の2段構成に作り替えています。
.dash-stats): 画面上部に4つ。上端にアクセント色の細い帯を入れ、数字が並んでも視線が迷わないようにしました。狭い画面では2列に落ちます.dash-grid): 各セクションを見出し付きのカード(DashboardCard)にし、repeat(auto-fit, minmax(330px, 1fr)) で幅が取れるときは自動で2列以上に並ぶようにしました。これが縦長解消の本体です。700px以下では1列に戻りますShell の wide)。対話や読み物の画面は1行が長くなりすぎないよう880pxのままですメッセージ一覧・ダッシュボード・プロジェクト一覧・設定・入出金の8箇所から「← 戻る」を削除しました。いずれもヘッダー(デスクトップ)・下部タブ(モバイル)から直接行ける画面のため、戻るボタンは重複した導線でした。
下層画面の戻るは残しています: メッセージスレッド(→一覧)、プロジェクト詳細(→一覧)、診断結果の比較画面(→マイページ)。
「ドラフト版」「初期ドラフトです」の表記を削除し、12条 → 26条に全面的に書き直しました。上場企業の利用規約で一般的に備えられる条項構成に揃えています。
特にご要望のあった2点は、独立した条として厚く規定しました。
その他の追加条項: 第7条(秘密保持・退会後5年)、第10条(業務の遂行・再委託禁止・著作権の帰属・偽装請負の回避)、第11条(報酬・遅延損害金年14.6%)、第16条(利用制限および登録抹消)、第17条(退会・存続条項)、第19条(民法548条の4に基づく規約変更)、第21条(免責・賠償上限)、第22条(反社会的勢力の排除)、第23条(事業譲渡時の地位承継)、第24条(分離可能性)、第25条(東京地裁の専属的合意管轄)。
10項 → 16項に全面的に書き直し、個人情報保護法が事業者に求める公表事項を網羅しました。
企業側のAI課題診断は10問でした(人材側は既に5問)。登録までに離脱されるリスクを下げるため、企業側も5問に統一しています。
「メールアドレスとパスワードを入れる箱」だけの画面から、登録の先に何があるかが見える画面に作り替えました。
/company ページを参照する書き方にしています。実際の登記情報を直接記載する運用にする場合は lib/legalPrivacy.js を編集してください。また第10項の安全管理措置は実際に実装している内容のみを書いています。未実施の措置を書くと虚偽記載になるため、運用を追加したら都度更新してください人材側7件のフィードバックへの対応。
app/app/page.jsx から lib/axes.js に移しました。UIとAIプロンプトの両方が同じ定義を参照します設定画面に「職歴」と「自己紹介・実績」という実質同じ内容を書く欄が2つあり、DB上も careerHistory と bio に分かれていました。「職歴・実績」の1項目に統一しています。
careerHistory と bio の両方に同じ内容を書き込みます。bio は企業側の候補カードの紹介文・AI解析の入力として各所から参照されているため、列自体は残して互換を保っていますbio にしか入っていないため、careerHistory が空の場合は bio を初期値として表示します(保存すれば両方揃います)bio)だけがAIに渡っており、設定画面で職歴を書き足しても解析結果に反映されていませんでした設定画面に振込先口座(金融機関名・支店名・預金種別・口座番号・口座名義カナ)を追加しました。
****567)。フルの値はDBにのみ存在し、APIからも返しません。変更時は入れ直しになりますlib/bankAccount.js、テスト15件)TalentBankAccount は Talent 本体とは別テーブルです。マッチング等の一覧取得で誤って読み出さないようにするためですアプリ画面のヘッダー: 42px → 54px(モバイルは32px → 40px)、ランディングページ: 38px → 50px。
スキルマップ(レーダー/棒グラフ)を画面の一番上に移しました。「スコアの見方(0〜5点=… / 6〜15点=…)」と補足説明は、点数を見たあとに確認するものなのでチャートの下に配置しています。
スキルマップに「経験を活かせる可能性がある業界」を追加しました(AIが確度の高い順に2〜4件+理由)。
lib/industries.js)app/app/page.jsx から lib/industries.js に移し、企業の登録フォーム・人材の業種経験・AIの候補生成で共通化しました4点の改善を入れています。
valueTags)も経歴解析のプロンプトに渡っていなかったため追加しています(対話形式の解析では既に渡していました)TalentSkillMap.axisEvidence)bio 列は残しています。将来的に careerHistory へ完全移行する場合は、参照箇所(候補カード・AI解析・メッセージ画面の人材詳細)をまとめて切り替える必要があります人材側4件・企業側2件のフィードバックへの対応。
.app-topbar というクラス名は使われていたものの、対応するCSSがどこにも定義されておらず、ヘッダーは通常のブロック要素としてスクロールで流れていました。position: sticky で上部に固定し、ロゴを左・操作(デスクトップはナビ+ログアウト、モバイルはログアウトのみ)を右に配置しています。半透明+背景ぼかしで、下のコンテンツが透けて見えます。
env(safe-area-inset-top))を考慮position: sticky は祖先に overflow-x: hidden があると効きません(その祖先がスクロールコンテナ扱いになるため)。.app-root がまさにその状態だったので、overflow-x: clip(スクロールコンテナを作らない)を後置で追加しています。未対応の古いブラウザ(Safari 15以前など)は従来どおり hidden にフォールバックし、その場合ヘッダーは固定されませんPOST/DELETE /api/talent/photoTalentPhoto.data、bytea)に保存します(稼働ログの成果物添付と同じ方針)。Talent 本体にBytesを持たせると、マッチングの人材一覧取得のたびに全員分の画像を読み込んでしまうため、別テーブルに分離しています。Talent.photoUpdatedAt(日時のみ)で写真の有無を判定しますGET /api/talents/[talentId]/photo。企業・管理者・本人のみ閲覧でき、未ログインには返しません(顔写真が公開Webに流出しないようにするため)「これまで経験してきた機能領域」「得意な働き方」「大切にしている価値観」は、選択肢をすべて並べたタグ列だったため画面が縦に伸びていました。MultiSelectDropdown を新設し、普段は選択中の内容と件数だけを表示、クリックで開いてチェックボックスで選ぶ方式に変更しています(複数選択のままです)。初回登録フロー・設定画面の両方に適用。
企業の支払い・人材の入金のスケジュールを新設しました。日付ルールは lib/paymentSchedule.js に純粋関数として実装し、テスト31件を追加しています。
| ルール | |
|---|---|
| 締め日 | 対象月の末日 |
| 企業の支払期日 | 締め日の翌月末 |
| 人材の入金日 | 締め日の翌々月15日(企業からの入金確認後に支払う建て付けのため、企業の支払期日より後に置いています) |
GET /api/projects/[id] が算出して返します)GET /api/paymentsInvoice)や支払い(Payout)を発行済みの月は、その確定金額とステータス(請求済み/入金済み等)を表示します。未発行の月は「予定」バッジ付きの概算ですご要望の「一覧 → 詳細 → メッセージ」という導線自体は既に実装済みだったため、一覧・詳細の情報量を補強しました。
lib/paymentSchedule.js の DEFAULT_SCHEDULE を変更してください(管理画面からの変更UIはまだありません)。画面上にも「予定」「概算」であることを明記していますEngagement)からの自動算出で、請求書・支払いの自動発行は行いません(従来どおり運営が管理画面から発行する運用です)。日割り計算・中途解約時の精算にも未対応です2件のフィードバックへの対応。
従来は未着手=グレー、進行中=ネイビー、完了=オレンジの白抜きバッジで、ネイビーとグレーが近く見分けづらいうえ、完了がサイトの主役色(オレンジ)という直感に反する配色でした。3状態を色相・塗り・記号の3点で区別するように変更しています。
| 状態 | 表示 |
|---|---|
| 未着手 | グレーの白抜きバッジ + ○ |
| 進行中 | オレンジのベタ塗りバッジ + ▶、行の背景もうっすらオレンジ |
| 完了 | 緑のベタ塗りバッジ + ✓、タイトルに取り消し線 |
lib/theme.jsx の TASK_STATUS_META に集約し、COLORS.success(緑)を追加しました。表記ゆれ(「着手中」/「進行中」)も「進行中」に統一しています。「タスク完了後に企業側が通知をするボタン」というご要望に対し、人材が完了を報告 → 企業が確認して承認 → 契約完了という一方向のフローとして実装しました。契約提案が企業起点(v3.9)なのに対し、完了は作業した人材起点にしています。
Project.status と Engagement.status をまとめて completed にし、人材の稼働枠(Capacity.currentCommittedHours)を解放します(契約が active のときのみ解放する二重解放ガード付き)。人材へ完了通知メールを送ります。【完了の差し戻し】 として残り、人材へメール通知されます。人材は対応後に再度報告できます。POST /api/projects/[id]/completion(action: request / approve / reject)。当事者チェック・ロールチェック・二重実行ガードはすべてサーバー側で行い、操作は監査ログ(AuditLog)に記録されます。Project に completionRequestedAt / completionNote / completionRejectedAt / completedAt を追加(カラム追加のみのため、ビルド時の prisma db push で本番DBへ自動反映されます)。lib/projectFlow.js に純粋関数として切り出し、テスト17件を追加しました(projectFlowStepIndex / projectStatusMeta / completionActionFor)。UIとAPIが同じ判定を参照します。v2.2で追加した POST /api/projects/[id]/rating(企業→人材の5段階評価)は、v5.1で /app/projects/[id] の独立実装ページを廃止した際に呼び出し元が失われ、UIから到達できなくなっていました。契約完了フローの最終段階として自然な位置なので、プロジェクト画面に星5段階+コメントの評価欄として復活させています(完了後は枠を緑で強調)。人材側からは受け取った評価を読み取り専用で確認できます。
npm test が現行のNode(v22)で Cannot find module '.../tests' となり実行できなくなっていたため、node --test tests/*.test.js に修正しました。全84件パスします。active に戻す運用になります。その場合 Project.completedAt は残ります)。5件のフィードバックへの対応。
PATCH /api/projects/[id]/plan(AI生成と同じサニタイズを通す)。PATCH/DELETE /api/projects/[id]/tasks/[taskId]、taskの所属プロジェクト検証付き)。GET /api/attachments/[id])。/app/projects/[id] の独立実装ページを廃止し、SPA本体(/app?project=)へのリダイレクトに一本化(v5.0で直したプラン形式ずれのような二重実装バグの再発防止)。メール内リンクもプロジェクト直行に。/app/projects/[id] ページがプランを配列として描画していたが、APIは {month1:[],...} 形式を返しており、生成成功後の描画で必ずクラッシュしていた。形式を統一して修正。Project.plan に保存され、企業・人材のどちらがいつ開いても表示される(人材はここを見れば自分のやることが分かる)。Month 1の項目はタスクへ、KPIはKPI欄へ自動登録する。ただし手入力済みのタスク/KPIがある場合は上書き・追加しない(データ保護)。AI出力はサーバー側でサニタイズ(件数・文字数・時間の上限)してから保存する。3件のフィードバックへの対応。
/api/matches/[matchId]/draft-message で画面表示後に非同期取得する方式に変更。取得中は「下書きを作成しています…(待たずに入力してもOK)」と表示し、ユーザーが入力を始めていたら下書きで上書きしない。「新着メッセージがあるとき、それがわかる表示になっているか?」への対応。従来はユーザー画面に新着表示がなかった(メール通知と管理画面のみ)。
Message.readAt カラムを追加(スレッドを開いた時点で相手からのメッセージに既読日時を記録)。ビルド時の prisma db push で本番DBにも自動反映される追加のみの変更。GET /api/messages/unread-count: 自分宛の未読総数を返す。クライアントは画面遷移時+60秒ごとにポーリングし、ナビの「メッセージ」に赤い数字バッジを表示(ヘッダー・モバイル下部タブの両方)。「メッセージから戻ると診断画面に戻ってしまう」「上部のボタンが見づらい」「人材側はスマホ利用が基本」というフィードバックへの対応。
public/logo.png を差し替え。サイトのヘッダー(LP・アプリ画面)は同ファイルを参照しているため自動で新ロゴになる。{サイトURL}/logo.png)に変更。見出し・情報テーブルの文字色をロゴのネイビー(#01204d)、ボタン・アクセントをロゴのオレンジ(#fc6000)に統一。「メールの文章がシンプルすぎる」というフィードバックへの対応。
lib/mailer.js に renderBrandEmail() を追加。BATTER BOXブランド(白・黒・グレー+オレンジ)のHTMLメールを、見出し・本文・情報テーブル(契約条件など)・引用ブロック(メッセージ抜粋)・CTAボタン・フッターで構成する。メールクライアント互換のためテーブルレイアウト+インラインスタイルで実装し、差し込む値はすべてHTMLエスケープする。v4.3の調査で判明した、より根本的な問題への対応。
本番テストで「最適な人材が出ない」というフィードバックを受けた対応。
lowMatchFallback)。企業→人材(/api/match/company)と人材→企業(/api/match/talent)の両方向に適用。本番テストで「AIの応答が遅い」「再試行が何度も出る」というフィードバックを受けた対応。
CLAUDE_MODEL / CLAUDE_FAST_MODEL で差し替え可能。lib/claude.js に、一時的な失敗(429レート制限・529過負荷・5xx・ネットワーク断)を最大2回まで自動でやり直す処理を追加。ユーザーに「再試行」ボタンが表示される頻度を大きく減らす。export const maxDuration = 60 を追加し、Vercelが処理を途中で打ち切らないようにした。「管理画面で新着メッセージ・契約等の動きが分かりやすいようにしたい」「メールも届くようにしたい」との要望に対応しました。
AdminNavを非同期コンポーネント化し、以下の件数を取得してナビゲーション項目に赤く光る(パルスアニメーション付き)バッジで表示するようにしました。
Engagement.status === "proposed")status === "new")注記: 「メッセージ」の件数は、まだ既読管理の仕組みがないため、「直近24時間以内の新着」という簡易な基準にしています。将来的に「最終閲覧日時」のような正確な既読管理をするには、別途設計が必要です。
以前(v3.8)は新着メッセージ・パスワード再設定のみでしたが、以下にも拡大しました。
いずれも、送信に失敗しても本体の処理(契約提案・回答・問い合わせ受付)は継続する設計です。実際にメールが届くようにするには、引き続きRESEND_API_KEYをVercelの環境変数に設定する必要があります(v3.8参照)。まだ設定していない場合は、これらのメールも「コンソールログに出力されるだけ」の状態のままです。
「契約条件のパターンをAIで複数出すのはどう?」という提案を受け、実装しました。
企業が「契約条件を提案する」フォームを開いた際、「AIに提案してもらう」ボタンから、診断結果の課題の優先度・対象人材の役職と経験年数・企業の年商規模をもとに、「お試し導入」「標準」「本格導入」の3パターン(月間稼働時間・企業支払額・一言の理由)をAIに生成させ、クリックした内容がそのままフォームに反映されるようにしました(その後も自由に編集できます)。
lib/contractSuggestionPrompts.js: 提案生成用のプロンプトPOST /api/matches/[matchId]/suggest-contract-terms: 企業アカウントのみ実行可重要な設計判断: このプラットフォームには、まだ実際の成約金額の蓄積データがありません。そのため、AIが提示する金額は「一般的な感覚に基づくたたき台」であり、実際の相場を反映したものではありません。これを隠さず、提案が表示されるたびに「実際の成約相場データがなく、あくまで参考値」という注記を必ず表示するようにしています。将来的に実際の契約データが蓄積されれば(データを競合優位性に変える仕組み・AxisPerformanceと同様の考え方で)、より精度の高い提案に育てられる余地があります。
「契約は人材側ではなく企業側から送れるようにして、契約に進んだ後、内容確認・委託契約書を締結・プロジェクト進行の流れにしたい」との要望を受け、契約成立までの流れを作り替えました。
v3.5で実装した「企業・人材どちらからでも“契約に進みたい”と意思表示でき、双方揃ったら運営が手動で契約条件を入力して成立させる」という、運営が仲介する双方向の仕組みでした。
POST /api/matches/[matchId]/propose-contract、企業アカウントのみ実行可)。人材への支払額は標準料率(60%)を目安に自動計算されるが、個別に指定も可能POST /api/matches/[matchId]/respond-contract、人材アカウントのみ実行可)。辞退した場合は提案が削除され、企業は条件を変えて再提案できる/app/contracts/[engagementId]ページで、甲乙・契約形態・対象課題・稼働時間・報酬額等をまとめた「業務委託契約 内容確認書」を、契約成立後いつでも確認・印刷/PDF保存できるようにした運営が毎回契約条件を手動入力する必要はなくなり、企業・人材の間で完結するセルフサービス型の流れになった。ただし、以下は維持している。
acceptMatchAction)は、緊急時のフォールバックとして残した(企業が既に提案中の契約がある場合でも安全に上書きできるよう修正済み)/admin/matches・企業/人材の詳細ページ・/admin/messagesに、「人材の回答待ちの契約提案」件数・バッジを表示し、運営が状況を把握できるようにしているv3.5で追加した「双方が契約に進みたい意向を示す」ボタン(Match.companyReadyAt/talentReadyAt)による仕組みは、この新しいフローに置き換わったため、UI・APIエンドポイント(/api/matches/[matchId]/ready)を削除した。データベースのカラム自体は(移行リスクを避けるため)残しているが、今後使用しない。
重要な注記(法的な位置づけについて): 「委託契約書を締結」という言葉を使っていますが、実装したのは電子署名等による法的拘束力のある契約締結ではなく、プラットフォーム上で合意した契約条件を記録・確認できる画面です。確認書の画面にもその旨を明記しています。正式な契約書面が必要な場合は、以前作成した契約書ひな形(docx)を用いて別途締結することを想定しています。この点はビジネス上・法務上の判断が必要な部分だと思いますので、実際の運用前に確認いただくことをおすすめします。
正直な注記(動作確認について): この一連の変更は、企業側・人材側それぞれのアカウントを使って実際に「提案→承諾→契約成立→プロジェクト開始」まで通しで動かした確認はできていません。特に、既に契約が成立している状態から人材が辞退した場合の挙動(現状は起こり得ない導線ですが)や、企業が同じ相手に再提案する場合の表示更新のタイミングは、実際に触ってみないと分からない部分です。
「メッセージがあると、登録のメールアドレスに通知がいくようにしたい」との要望に対応しました。あわせて、これまで「ログ出力のみ」で未実装だったパスワード再設定メールも、同じ仕組みで実際に送信されるようにしています。
lib/mailer.js: ResendのAPIを直接fetchで呼び出す送信ヘルパー。追加のnpmパッケージは不要(Anthropic APIの呼び出し方と同じ考え方)lib/siteUrl.js: メール本文内のリンクを常に絶対URLで組み立てるためのヘルパー(NEXT_PUBLIC_SITE_URL → VERCEL_URL → ローカル用フォールバックの優先順位)POST /api/messagesにメール送信を追加。新しいメッセージが送られると、受信側(相手)の登録メールアドレスに、送信者名・本文の冒頭・ログインリンクを含む通知メールを送るようにしたPOST /api/auth/forgot-passwordも同じ仕組みで実際にメールを送るように変更(これまでの「ログ出力のみ」から置き換え)RESEND_API_KEYを設定しない限り、これまで通りコンソールログに出力されるだけで、実際のメールは送信されません。 実際に送るには、以下の設定が必要です。
RESEND_API_KEY としてそのキーを追加EMAIL_FROMにそのドメインの送信元アドレス(例: [email protected])を設定できます。認証していない場合は、Resendのテスト用アドレス([email protected])がデフォルトで使われます正直な注記: 現状は、メッセージが送られるたびに毎回通知メールが送信される設計です。短時間に何度もやり取りが続く場合、通知が頻繁に届いて煩わしく感じられる可能性があります。将来的に「一定時間まとめて1通にする」といった調整が必要になるかもしれません。また、この機能一式は実際にResendと接続して送信を確認したことがなく、Resend側のAPIレスポンス形式が実際に想定通りかも未検証です。
「企業も人材もやり取りしているDMが一元管理できる状態にしたい」との要望に対応しました。v3.3で個別のメッセージ閲覧ページ(/admin/matches/[matchId]/messages)は作りましたが、そこにたどり着くには企業ページ・人材ページから該当のマッチングを探して開く必要があり、「全体を俯瞰する場所」がありませんでした。
/admin/messages: メッセージが1件以上あるやり取りを、最終メッセージが新しい順に一覧表示。企業名・人材名での検索、20件区切りのページネーションに対応これで、企業ページ・人材ページ経由の個別アクセスと、/admin/messagesからの一元的な俯瞰の両方が可能になりました。
注記: 現状の実装は、メッセージ件数がまだ大きくない前提で、並び替え・検索をアプリケーション側(JavaScript)で行っています。将来的にメッセージ数が大幅に増えた場合は、DBクエリ側でのソート・絞り込みへの見直しが必要になる可能性があります。
「人材も企業も管理側で承認するフローになっているが、機能を残したまま基本は承認の状態にしてほしい」との要望を受けて対応しました。
まず、正確な現状の共有です: 実は承認フローが存在していたのは人材側のみでした。企業側には元々承認の仕組み自体がなく、企業アカウントは登録後すぐに診断・利用が始められる設計でした。
人材側について、以下のように変更しています。
Talent.statusのデフォルト値を"pending"(審査中)→"approved"(承認済み)に変更。新規登録した人材は、これまで管理者が手動で「承認」ボタンを押すまでマッチング候補に出てこなかったが、今後は登録直後から候補に表示されるようになる/api/talent/analyze・/api/talent/dialogue/answer(スキル解析・自己分析対話の完了時にDB保存する処理)の内部フォールバック値も、新しいデフォルトに合わせて変更これにより、通常運用では人材登録のたびに承認作業をする必要がなくなりつつ、問題のある人材が現れた場合に管理画面からいつでも除外できる機能は維持されています。
「企業と人材がメッセージでやりとりした後、具体的にどう進めるかのフローが確立されていない」という指摘に対応しました。これまでは、メッセージを送り合った後の展開が完全にブラックボックス(運営が裏で個別に動くこと前提)になっており、企業・人材のどちらにも「次に何をすればいいか」が見えていませんでした。
MatchモデルにcompanyReadyAt・talentReadyAtを追加POST /api/matches/[matchId]/ready: メッセージ画面から、企業・人材それぞれが「契約に進みたい」意向を表明できるようにした(いきなり自動契約にはせず、運営の確認を挟む設計は維持)/admin/matchesの最上部に、双方が意向を示した(=すぐに契約化の判断が必要な)マッチングだけを集めた一覧を新設これにより、「診断→提案→メッセージ→(ここが空白だった)→契約→プロジェクト開始」という流れの空白部分に、明確な次のアクションが生まれました。実際の契約化(金額・稼働時間の確定)は、これまで通り運営が企業ページから行う設計は変更していません(いきなり自動で契約にはしない、という既存の方針を尊重しています)。
正直な注記: この機能も、実際にVercel上で動かした確認はまだできていません。特に、企業側・人材側それぞれのアカウントで実際に「契約に進みたい」を押して、双方揃った時にバナーとadmin側の表示が正しく連動するかは、2つのアカウントを使って両方の立場から試してみる必要があります。
「サンプルで入力した企業や人材もアカウント削除できるようにしたい」との要望に対応しました。企業・人材ともに、関連するデータ(診断履歴・スキルマップ・マッチング・契約・プロジェクト・タスク/KPI/稼働ログ・請求書・支払い・企業からの評価・メッセージ・ログインアカウント)が芋づる式に連なっているため、慎重に実装しています。
lib/adminDelete.js: deleteCompanyCascade() / deleteTalentCascade()。外部キー制約に違反しない順序(子→親)で関連データを削除した上で本体を削除する。すべて単一のPrismaトランザクションで実行し、途中で失敗した場合は何も削除されない状態に戻るadminDeleteCompanyAction / adminDeleteTalentAction: 取り消せない操作のため、確認欄に会社名/氏名を正確に入力しないと実行できないようにしている(JavaScriptのconfirm()ダイアログは使わず、サーバー側で入力値の一致を検証する方式。GitHubのリポジトリ削除等と同様の「名前を入力して確認」パターン)正直な注記: この機能は今回のセッションの中で最も破壊的な操作です。関連テーブルの数が多く(11種類前後)、削除順序を手作業で組み立てているため、スキーマの構造を見落として一部のデータが削除されずに残ってしまう(あるいは逆に、外部キー制約でエラーになり削除自体が失敗する)可能性があります。**本番の実データに対して使う前に、必ずテスト用のダミー企業・ダミー人材を1件作成し、実際に削除して問題なく消えるかを確認してください。**万が一エラーになった場合は、エラーメッセージを教えていただければ原因を特定します。
「運営の管理画面でもう少し詳細に管理できるようにしたい」との要望を受け、現状の棚卸しを行った上で、指摘した6項目すべてに対応しました。
/admin/users)User.disabledAtを追加。停止中はログイン不可、かつ既にログイン中のセッションも次のリクエストから即座に無効になるようgetCurrentUser()側でもチェックしている)/admin/companies・/admin/talentsに、名前での検索(Postgresの大文字小文字を区別しないcontains検索)と20件区切りのページネーションを追加。件数が増えても実用に耐える設計にした。
adminUpdateCompanyAction・adminUpdateTalentAction: 表記ミス等を運営が直接修正できる編集フォームを、それぞれの詳細ページに追加adminUpdateCapacityAction: 人材の同時受託上限(社数)・現在の稼働時間を直接編集できるフォームを追加(これまでは表示のみだった)/admin/projects: 全プロジェクトのタスク・KPI進捗、企業からの評価(★)を横断的に一覧表示/admin/matches/[matchId]/messages): 企業ページ・人材ページの各マッチング行から「メッセージを見る」で開ける読み取り専用ビュー(運営はここから送信できない設計。トラブル対応時の内容確認を主目的としている)新規/admin/settings。これまでlib/pricing.jsにPLATFORM_FEE_RATE = 0.4とハードコードされ、UI上も「40%」という文字列が直接書かれていただけでしたが、PlatformSettingという設定値テーブルを新設し、管理画面から実際に変更できるようにした(未設定時は引き続き40%にフォールバックするため、既存動作は変わらない)。
企業一覧・人材一覧それぞれに「CSVダウンロード」ボタンを追加(検索条件を反映した状態でダウンロード可能。Excelでの文字化けを避けるためUTF-8 BOM付き)。
正直な注記: 今回追加した機能(特にユーザー停止・パスワードリセット代行)は、既存アカウントの認証フローに直接影響する変更です。構文チェック・自動テストは通っていますが、実際に自分のアカウントを停止して本当にログインできなくなるか、停止解除でまた入れるようになるか、といった検証はできていません。次にデプロイした際は、テスト用の使い捨てアカウントで一度試してから本番アカウントに適用することをおすすめします。
実際に登録したユーザーから「10問答えるのは大変」というフィードバックがあったため、対応しました。
MAX_TALENT_DIALOG_TURNS)。企業側の対話ターン数(10問)とは別の定数として切り出し、フロントエンドの表示用定数(TALENT_DIALOG_TURNS)も揃えて修正。以前、企業側で「表示上のターン数と実際の上限がズレる」不具合を経験しているため、同じ轍を踏まないよう2箇所を必ず揃えている/api/talent/axis-deep-dive)企業側の対話(10問)・深掘り(3問)は変更していません。
「任意記述だけでなく選択式も用意してほしい」「もう少し自己分析できる質問がほしい」「質問数10個・項目ごとに深掘りできるように」というフィードバックを受け、企業側と同じ設計思想を人材側にも導入しました。
自由記述の「大切にしていること」に加えて、10個のタグ(スピード重視・着実な積み上げ・現場主義・データドリブンな意思決定 等)から選べる選択式を追加しました(VALUE_OPTIONS)。自由記述と選択式は併存し、どちらも保存されます(Talent.valueTags / Talent.values)。
これまで人材側は、静的なフォーム入力→AIへの単発の解析リクエスト、という一発勝負の流れでした。これを、企業側と同じ「10問・4択・reflection付き・タクトとの対話」形式に変更しました。
lib/talentDialoguePrompts.js: 企業側dialoguePrompts.jsと対になる人材版。ただし診断ではなく自己分析支援という位置づけで、システムプロンプトは「本人が気づいていない強みを引き出すキャリアコンサルタント」という人格にしているPOST /api/talent/dialogue/start・POST /api/talent/dialogue/answer: 企業側の/api/diagnosis/start・/api/diagnosis/answerと同じ構造(未ログインでも進められ、最終ターンでログイン中ならDB保存)StepTalentDialogue: StepDialogと同じUI(reflectionの吹き出し、タクトの名前表示等)を人材版として実装TALENT_STEPSのラベルを「AI解析」→「AI自己分析」に変更。/appのメインフロー・/join・マイページの再解析、すべてで新しい対話フローに統一企業側で実装した軸ごとの深掘り機能を、人材のスキルマップ結果画面にも追加しました。
POST /api/talent/axis-deep-dive: 企業側/api/diagnosis/axis-deep-diveの人材版(スコア範囲は0〜30)StepTalentSkillMapに「10軸すべての詳細を見る・項目ごとに深掘りする」を追加し、各軸をTalentAxisDeepDiveコンポーネントで深掘りできるようにした旧来の単発解析エンドポイント(POST /api/talent/analyze)は、念のため削除せずそのまま残しています。ただし現在、UIのどこからも呼び出していません(実質的に新しい対話フローに一本化されています)。
正直な注記: 今回の変更は企業側の対話機能と同じ設計パターンを踏襲しているため、比較的リスクは低いと考えていますが、人材側のAI対話・深掘り機能を実際にVercel上で動かして確認したことは一度もありません。特に、未ログイン(/join)の状態で対話→スキルマップ表示→アカウント作成、という一連の流れが問題なく通るかは重点的に確認していただきたい部分です。
「人材側のスキルを正確に知る必要がある」「業種・役職の幅を広げてほしい」「パーソナルな部分も把握したい」「得意・苦手も分析できるように」というフィードバックを受け、人材の経歴入力を拡張しました。
これまでAIの分析結果は「強み」(スコアが高い軸)のみを示していましたが、相対的にスコアが低い2軸を「これから伸ばしていきたい領域」として、否定的にならない建設的な言い方であわせて分析するようにしました(TalentSkillMap.growthAreas)。スキルマップ画面・マイページ・管理画面のすべてに反映しています。
TalentモデルにexperiencedFunctions・workStyleTags・valuesを追加TalentSkillMapモデルにgrowthAreasを追加lib/axes.jsにsanitizeGrowthAreas()を追加(AIの不正な出力に対する防御)lib/talentPrompts.jsの分析プロンプトを、申告された経験機能領域を踏まえた採点・growthAreas生成に対応させて全面改訂/api/talent/analyze・/api/talent/claim・/api/talent/profile・/api/me/profileを新フィールドに対応業種・役職そのものの選択肢は、v2.6で企業/人材共通の35業種・v2.7で拡充した30種類の役職リストを既に使っており、今回はそれに加えて「複数の経験機能領域を選べる」ようにすることで、単一の役職・業種だけでは表現しきれない経歴の幅をカバーする方針にしました。
実際に使ってみた結果を踏まえて、対話の速度・完了バグ・トーン・情報の見やすさを一通り見直しました。
callClaudeJSONが用途によらずmax_tokens: 2000固定だったため、対話ターン数を15に増やした結果、対話ログが長くなり最終スコアリング(10軸のscores・axisNotes・topIssueDetails・summaryを一度に出力)がその上限を超えて打ち切られ、JSONが壊れることがありました。max_tokensを呼び出し側で指定できるようにし、1問ごとの質問生成は700(体感速度向上も兼ねる)、最終スコアリングは3500に分離しました。
MAX_DIALOG_TURNS、フロント/バックエンド両方)診断結果画面(Growth Map)の「10軸すべての詳細分析」から、各軸ごとに「この項目を深掘り」ボタンで3問までの短いミニ対話を行えるようにしました。深掘りの結果、その軸のスコア・分析コメントが更新されます(POST /api/diagnosis/axis-deep-dive)。ログイン中の企業アカウントであれば、更新結果はDBにも反映されます。
上部のステップ表示から「企業情報」「経歴入力」に戻った際、入力済みの内容が消えてしまう不具合を修正しました。StepCompany・StepTalentInputがinitialFormを受け取り、既存の入力を復元します。
人材が企業からメッセージを受け取った際、「受けるかどうかの判断材料が少ない」というフィードバックを受け、メッセージ画面に相手企業の詳細(業種・従業員数・成長段階・年商・課題TOP3)を表示するパネルを追加しました。同じ仕組みで、企業側が人材からのメッセージを見る際にも相手の経歴・強みが表示されます(GET /api/matches/[matchId]/context)。
「初期ドラフトにつき専門家レビューが必要」という注記を削除し、確定版として表示するようにしました。
正直な注記: 深掘り機能・相手詳細パネルはこのセッションで新規に実装した機能のため、他の機能以上に実際の動作確認ができていません。特にAIのキャラクター性(「タクト」らしい口調になっているか)は、実際に対話してみないと判断が難しい部分です。
上部のステップ表示(企業情報/AI課題診断/Growth Map/人材提案)のうち、完了済みのステップをクリックすると、その画面に戻れるようになりました(/appのメインフロー・/diagnose・/joinすべてに適用)。企業側のステップ名「スキルマップ」も「Growth Map」に変更しています(人材側の「スキルマップ」表記はそのまま — こちらは人材個人の強みを表す言葉として適切なため)。
候補一覧のカードをクリックすると、これまで一覧の中に埋め込まれていた情報(役職・業種経験・実務経験年数・MATCH%の内訳・解決できる課題)を、独立した詳細画面としてまとめて確認できるようになりました。「メッセージを送る」はこの詳細画面の中の行動として位置づけ、一覧からの直接送信はできない設計に変更しています。GET /api/match/companyのレスポンスにyears(実務経験年数)を追加しました。
AIが下書きしたメッセージを、以前は直接編集可能な入力欄に流し込んでいましたが、まず読み取り専用のプレビューとして表示し、「この内容で送信」「編集してから送信する」の2択を明示するようにしました。編集を選んだ場合のみ、通常の入力欄に切り替わります。
これまでの対話は、質問→回答→次の質問、と機械的に進む構成でした。以下のように見直しています。
buildDialogNextQuestionPromptのJSON出力にreflectionフィールドを追加し、/api/diagnosis/start・/api/diagnosis/answerのレスポンスに含めるようにしましたStepDialog)では、reflectionを質問とは別の吹き出し(斜体・枠なし)として先に表示し、その後に質問の吹き出しを表示する2段階の見せ方にしていますMAX_DIALOG_TURNSが別途ハードコードされており、サーバー側の値(6)とズレていた(4のまま)不具合も、あわせて修正しています(質問数の表示「質問 X/Y」が実際の運用と食い違っていました)VC用語(シリーズA等)やARRという言葉は、外部資本を入れていない企業には馴染みが薄いというフィードバックを受け、対応しました。
CompanyモデルにfundingType(independent | vc)フィールドを追加し、DBに保存。企業設定画面(ProfileFieldsCompany)でも同様に選択可能21種類→35種類に拡充。士業・介護福祉・冠婚葬祭・設備工事・卸売商社・飲食店経営など、従来手薄だった伝統的な業種・中小企業向けの選択肢を追加しました。
lib/dialoguePrompts.jsを見直しました。
なお、10軸の構造自体(finance_raiseという軸名を含む)は変更していません。軸名を変えると影響範囲が非常に広い(AIプロンプト・マッチングロジック・シードデータ・管理画面表示など)ため、今回はプロンプトの指示内容で対応する形にとどめています。
役職・実務経験年数・業種業界・従業員数・事業フェーズ・売上規模の選択肢が狭いとのフィードバックを受け、企業/人材フォーム共通で使うINDUSTRY_OPTIONS(21業種)を新設し、単一ソースとして両フォームで共有するようにしました。役職は30種類(責任者級15+マネージャー級15)に拡充、実務経験年数・従業員数・事業フェーズ・売上規模もより一般的な区切りに広げています。
管理者としてログイン中に表示していた「管理画面へ」ボタンを、「ログイン」に変更しました。
見た目のトーン(配色・ヒーロー背景)はそのまま維持し、構成のみ見直しました。
新しいセクション順: Hero → Growth Mapプレビュー → 実績数値帯 → 3ステップ → よくある経営課題(新規) → マッチング後の機能紹介(新規) → 正社員採用との比較 → 企業/人材向けパネル → 人材プロフィール例 → FAQ → 最終CTA
なお、配色・レイアウトそのものを変えた3つの別デザイン案(エディトリアル調・ダーク調・Bento風)も検討用に作成しましたが、既存のトーンを維持する方針となり不採用としています。
以前は、ランディングページのCTAを押すと必ず/signup(企業/実務経験者を選ぶ新規登録画面)に行き、そこでアカウントを作ってから初めて診断が始まる流れでした。「無料で会社を診断する」を押したのに新規登録画面が出て、しかも関係ない「実務経験者として登録」の選択肢まで見える、という分かりにくさがありました。
これを、先に無料で診断・スキルマップ作成を体験してもらい、最後にアカウント作成(=そのままログイン)という順番に変更しました。
/diagnose — 未ログインで進められる企業向けAI課題診断。会社情報入力→AI対話→Growth Map表示→最後にメールアドレス・パスワードを入力してアカウント作成、の4ステップ/join — 未ログインで進められる実務経験者向けスキルマップ作成。経歴入力→AI解析→スキルマップ表示→アカウント作成、の4ステップ/diagnoseへ、「人材として登録する」「無料でスキルマップを作る」は/joinへリンクするよう変更(ヘッダーの汎用的な「無料で始める」ボタンのみ、従来通り/signupの選択画面のまま)/api/auth/signupでアカウント作成→即座にセッションが張られ、/appにログイン済みの状態で遷移する)実装の考え方: /api/diagnosis/start・/api/diagnosis/answer・/api/talent/analyzeは、認証を必須ではなくした(未ログインでもAI診断自体は進められる)。ただし未ログイン中は診断結果をDBに保存せず、ブラウザ側(React state)に保持しておくだけにしている。診断が完了しアカウントを作成したタイミングで、新設したPOST /api/diagnosis/claim・POST /api/talent/claimが、保持していた診断結果をまとめてそのアカウントに保存する。企業/実務経験者アカウントで既にログインしている場合の挙動(その場で都度保存する、再診断は履歴として積み上がる等)は変更していない。
/diagnose・/joinのUIは、既存の/app内のStepコンポーネント(StepCompany・StepDialog・StepSkillMap等)をそのままexportして再利用しており、UIロジックの二重管理を避けている。
企業・実務経験者のログイン先が同一だったため分離しました。
/login — ログイン先を選ぶ画面(企業として / 実務経験者として)/login/company — 企業専用のログインフォーム/login/talent — 実務経験者専用のログインフォーム/login/admin — 管理者用のログインフォーム(一般公開のログイン画面には出さず、/admin配下への未ログインアクセス時にのみ案内される直リンク)POST /api/auth/loginにexpectedRoleを渡せるようにし、ログインしたアカウントの実際のロールと一致しない場合は「このメールアドレスは〇〇アカウントとして登録されています」というエラーを返すようにしました(例: 実務経験者用ログイン画面に企業アカウントのメールアドレスを入力した場合など)。3つのログインフォームは同じRoleLoginFormコンポーネントを共有しています。
トップページのヒーローセクションに、アップロードされたイラスト(public/hero-illustration.jpg)を背景として使用しています。
配色・フォントを一新しました。以前は「診断ターミナル」を思わせる深いネイビー×ティール×琥珀色でしたが、一度「触りやすく、優しい印象」を意図した温かみのあるアイボリー×ローズ×ゴールドの配色に変更し、v1.4で現在のBATTER BOXブランド(ネイビー×オレンジ)に再度変更しています。見出しには丸みのある「M PLUS Rounded 1c」、本文には「Zen Kaku Gothic New」を使用し、ボタンはピル型(角丸最大)にしています。
配色トークンはlib/theme.jsxのCOLORSオブジェクトに集約されており、キー名(teal/amber等)は初期実装からの互換のため維持していますが、値そのものはBATTER BOXのブランドカラーです。管理画面(/admin)は社内利用前提のため、あえて元の実務ツール的なダーク配色のまま残しています(lib/adminTheme.jsxは配色を変更していません)。
レスポンシブ対応として、フォーム内の2カラムレイアウトは.two-colクラス経由で640px以下では1カラムに折り返すようにしました。ヒーロー見出しはclamp()で画面幅に応じてサイズが縮小します。ただし、実際にブラウザ(特に実機)で全ページを確認したわけではないので、細部の崩れがあれば教えてください。
/company(会社概要)は、https://befree-inc.com/about/ に掲載されている運営会社の実際の情報(会社名・所在地・資本金・有料職業紹介事業許可番号等)を反映しています(サービス名の表記のみ「BATTER BOX」に変更済み)。
npm install
cp .env.example .env.local
# .env.local の ANTHROPIC_API_KEY と DATABASE_URL を設定
npx prisma migrate dev --name init # テーブル作成 + シードデータ投入(候補人材・候補企業・管理者アカウント)
npm run dev
npx prisma migrate dev を実行すると、コンソールに管理者アカウントのメールアドレスとパスワードが出力されます(SEED_ADMIN_EMAIL / SEED_ADMIN_PASSWORD を.env.localで指定していない場合はランダム生成されるので、必ず控えてください)。このアカウントで /login からログインすると /admin に入れます。
自動テストは npm test で実行できます(DB接続不要、追加パッケージのインストールも不要です)。
http://localhost:3000 を開くと、ログイン/新規登録の画面が表示されます。「企業として登録」「実務経験者として登録」のどちらかを選んでアカウントを作成すると、以降はそのロールの画面に自動的に案内されます。
app/
page.jsx … ユーザー向けUI本体(企業側4ステップ・人材側4ステップ・メッセージ)
login/page.jsx … ログイン画面
signup/page.jsx … 新規登録画面(企業/実務経験者ロール選択)
legal/terms/page.jsx … 利用規約(ドラフト)
legal/privacy/page.jsx … プライバシーポリシー(ドラフト)
admin/
layout.jsx … 管理画面の認証ガード(role=admin必須)+ログアウトバー
page.jsx … ダッシュボード
companies/ … 企業一覧・詳細
talents/ … 人材一覧・詳細
matches/ … マッチング一覧
engagements/ … 契約一覧・成果記録
api/
auth/signup, login, logout, me/route.js … 認証
diagnosis/start, answer/route.js … 企業側AI対話(認証必須・role=company)
talent/analyze/route.js … 人材側AI解析(認証必須・role=talent)
match/company, match/talent/route.js … マッチング(認証必須、稼働上限フィルター適用)
matches/connect/route.js … メッセージスレッドの起点となるMatch作成
messages/route.js … メッセージ送受信
messages/threads/route.js … 自分の会話一覧(受信箱)
prisma/
schema.prisma … データモデル定義(User/Session/Message含む)
seed.js … 初期データ投入(候補人材・候補企業・管理者アカウント)
lib/
axes.js … 共通10軸の定義・採点基準(TALENT_SCORE_RUBRIC)
matching.js … scoreMatch()(BeFree_マッチングロジック設計.md準拠)
capacity.js … 稼働上限による足切りフィルター
claude.js … サーバー専用のClaude API呼び出しヘルパー
auth.js … パスワードハッシュ・セッション管理
matchAccess.js … メッセージ機能のアクセス権限チェック
prisma.js … Prisma Clientのシングルトン
theme.jsx / adminTheme.jsx … デザイントークン(ユーザー向け/管理画面)
dialoguePrompts.js … 企業側対話のプロンプト(業種特化・軸トラッキング対応)
talentPrompts.js … 人材側解析のプロンプト(採点基準明示)
actions.js … 管理画面のServer Actions(すべてrole=admin必須)
/signup で自分でアカウントを作成します(メール+パスワード、パスワードはNode組み込みのscryptでハッシュ化。追加ライブラリなし)。prisma/seed.js 経由でのみ作成されます(意図的な設計です)。companyId/talentIdを信用せず、セッションから取得したアカウントの持ち主情報を使います。同じアカウントで再度診断/解析すると、新しいCompany/Talentを作らずに既存プロフィールを更新し、スキルマップだけ履歴として追加します。マッチング候補一覧の「メッセージを送る」ボタンを押すと、/api/matches/connect がその組み合わせのMatchを作成(または既存のものを再利用)し、メッセージスレッドを開きます。スレッドは5秒間隔でポーリングして更新します(WebSocketは未導入)。ヘッダーの「メッセージ」ボタンから、自分が関わっている全スレッドの受信箱にアクセスできます。アクセス制御は、そのMatchの企業側/人材側どちらかのアカウント本人(または管理者)のみに限定しています。
Capacity.maxConcurrentEngagements(デフォルト3社)に対して、進行中(status: active)の契約数が上限に達している人材は、マッチング候補(/api/match/company)から自動的に除外されます。契約が成立すると Capacity.currentCommittedHours に稼働時間が加算され、契約が一時停止・完了になると解放されます。
/legal/terms(利用規約)と /legal/privacy(プライバシーポリシー)を用意しています。弁護士レビュー・関連ドキュメントの整備は完了している前提として扱っています。
「データを蓄積するほど、競合が同じサービスを作っても追いつけない」状態を目指し、蓄積データをマッチング精度の向上に実際にフィードバックする仕組みを実装しました。
AxisPerformanceモデル: 各軸(10軸それぞれ)について、その軸が「一致軸」として採用されたマッチングの平均成果スコア(EngagementOutcome)を集計し、学習係数(0.7倍〜1.3倍)を算出しますlib/learning.js: 学習係数の計算ロジック(外部依存なし、純粋関数としてテスト可能)。サンプル数が3件未満の軸は中立値(1.0)のまま据え置き、少数データでの過学習を防ぎます。サンプルが増えるほど、算出された係数に近づく設計(ベイズ的な縮小)ですscoreMatch()への統合: lib/matching.jsのスコア計算式に、学習係数を重み付けとして組み込みました。既存の呼び出し(係数省略時)は従来と全く同じ結果を返すため後方互換です/admin/insights: 軸ごとの学習係数、フェーズ別の平均スコア傾向などを確認できる画面。「学習係数を再計算する」ボタンで、蓄積された成果データから再計算できます(スケジューラは未導入のため手動トリガー)この仕組みは、実際の成果データ(EngagementOutcome)が蓄積されるほど精度が上がっていく設計になっています。同じ計算式を真似ただけの競合が現れても、BeFreeと同じ量の実績データを持たない限り、同じ精度のマッチングは再現できません。
未着手として残っていた項目のうち、⑬・企業からの評価・AIプロフィール改善案に対応しました。
ランディングページのヘッダーをサーバーコンポーネント化し(getCurrentUser()をサーバー側で呼ぶため、クライアント側フェッチによる表示のちらつきがありません)、以下のように出し分けています。
/app/projects/[id] の実URLルーティングこれまでプロジェクト詳細はapp/app/page.jsx内のSPA的な画面切り替え(view状態)でのみ表示され、URLを直接共有・ブックマークできませんでした。app/app/projects/[id]/page.jsxとして独立した実URLページを新設し、同じAPI(/api/projects/[id]等)を使って同等の機能(タスク・KPI・稼働ログ・コメント・90日プラン・AI月次レビュー・評価)を提供します。
既存の巨大なSPAファイルへの変更リスクを避けるため、コンポーネントを共有するのではなく独立した実装にしています(表示内容は基本的に一致しますが、コードは重複しています)。プロジェクト一覧の各カードに「共有可能なURLで開く」リンクを追加しました。
新規モデルTalentRating(プロジェクト単位で1件、企業側のみ記録・更新可)。POST /api/projects/[id]/ratingで1〜5の星評価とコメントを送信できます。人材ダッシュボードに平均評価が表示されます。
lib/profileSuggestion.js・POST /api/talent/profile-suggestion。現在の自己紹介・役職・スキルマップ上の強みをもとに、AIが3〜4個の具体的な改善提案を生成します。人材ダッシュボードの「改善案を生成」ボタンから、都度生成する方式です(保存はしません)。
TalentRatingは今回追加したモデルのため、実際にレビューして問題ないか、企業⇄人材どちらから見ても違和感のない評価の仕組みになっているかは、実際に使ってみないと分かりません/app/projects/[id]の独立実装は、既存のSPA版と機能・見た目をできる限り揃えていますが、コードが2箇所に分かれているため、今後の変更は両方に反映する必要があります未着手として残っていた項目のうち、⑨ダッシュボードと⑫FAQに対応しました。
GET /api/dashboardが、ログイン中のアカウントの役割に応じて必要な情報を1回のリクエストで集約して返します。ヘッダーの「ダッシュボード」から入れます。
「マイページ」とは役割が異なります。マイページは診断結果そのものの確認・再診断の起点、ダッシュボードはプロジェクトの進捗まで含めた「今の状況」を一覧する場所という位置づけです。
未対応の部分: 「企業からの評価」「AIによるプロフィール改善案」(人材向け)、「AIからの提案」の専用生成(企業向け、既存の課題TOP3で代替)は、評価の仕組みや追加のAI呼び出しが必要なため、今回は実装していません。
ランディングページに、料金・契約形態・診断の正確さ・支援後の進捗管理などについてのFAQを6問追加しました。あわせて、最終CTAセクション(黒背景の締めのセクション)も新設しています。
/app側は既にロール別の表示分岐がありますが、公開LP側のナビは意図的に簡素なまま)/projects/[projectId]の実URLルーティング残っていた最後の2項目に対応しました。これで、いただいた34項目のリニューアル依頼のうち、Priority 1〜3に該当する項目は一通り着手した状態になります(実運用での検証はまだ、という点は変わりません)。
/admin/talents/[id] に「あなたが最も価値を発揮する企業」欄を追加しました。対応フェーズ(シード/プレシリーズA/シリーズA/シリーズB以降)ごとに、一般的な従業員数・売上規模の目安を併記し、解決できる課題タグも表示するようにしています。この目安は統計データではなく、一般的なスタートアップのフェーズ区分に基づく参考情報です。「希望単価」等、まだ持っていない構造化データを使う項目は未対応です。
/admin/companies/[id]で、AIが算出した各軸のスコアを管理者が手動で修正できるようにしました(adjustCompanyScoreAction)。AIの診断が明らかに実態とずれている場合の是正用で、変更は監査ログに記録されます/admin/insightsで、軸ごとの学習係数(マッチングの重み付け)を管理者が手動で上書きできるようにしました(overrideAxisWeightAction、0.5〜1.5の範囲)。「学習係数を再計算する」を実行すると、この手動値は成果データに基づく自動計算値で上書きされます「単体でも大きいもの」とした4項目のうち3項目を実装しました。
Project(契約=Engagementと1:1)、ProjectTask、ProjectKPI、WorkLog(稼働ログ)、ProjectComment(進捗コメント・議事メモ)acceptMatchActionが同じトランザクション内でProjectも自動作成します。対象課題(targetAxis)は、企業の最新スキルマップで最もスコアが低い軸を引き継ぎますGET /api/projects(自分が関わるプロジェクト一覧)、GET/PATCH /api/projects/[id]、タスク/KPI/稼働ログ/コメントそれぞれの作成・更新エンドポイント。すべてlib/projectAccess.jsでアクセス制御(そのプロジェクトの企業側/人材側当事者、または管理者のみ)/projects/[id]という実URLでの直接アクセスは、既存アプリのSPA構成(app/app/page.jsx内のview状態で画面を切り替える設計)に合わせ、実際のNext.jsルーティングとしては実装していません(URLを直接共有・ブックマークすることはできません)lib/ninetyDayPlan.js・POST /api/projects/[id]/plan。対象課題・今月の目標・月間稼働時間をもとに、Month1(現状整理・設計)/Month2(実行)/Month3(定着・改善)それぞれ3〜4項目の実行プランをAIが生成します。保存はせず、都度生成する方式ですlib/projectAI.js・POST /api/projects/[id]/summary。現在のタスク・KPI・稼働ログ・コメントから、今月実施したこと/達成KPI/未達KPI/課題/来月の優先事項/継続すべき施策/やめるべき施策の7項目でレビューを生成します。「AI進捗サマリー」(項目13)と「AI月次レビュー」(項目14)は仕組みが似ているため、1つの生成機能に統合しています。記録が少ない場合はAIが「記録が少なく判断できません」のように正直に書くよう指示しています上記3項目は、Prisma(DB)またはClaude API(生成AI)に依存する処理が中心のため、既存の「外部依存のない純粋関数」を対象にした自動テストの対象にはなりにくい部分です。構文チェック(94ファイル)と既存の自動テスト65件が壊れていないことは確認済みですが、プロジェクト機能自体の新規テストは今回追加できていません。実際にブラウザで動かして初めて確認できる部分が今回は特に多いです。
前回のリニューアル対応で「比較的すぐ着手できるもの」とした3項目のうち2.5項目を実装しました。
lib/matchBreakdown.js。課題領域一致(30点)・会社フェーズ(20点)・業界経験(15点)・役職経験(15点)・スキル一致(15点)・稼働条件(5点)の6項目に分解して算出する純粋ロジック(テスト9件)。企業側の人材提案画面で「MATCH ○○%」をクリックすると内訳が展開表示されます。役職経験はleadership軸のスコアを代理指標として使用しています(実際の役職・在籍企業規模等の構造化データはまだ持っていないため)TalentSkillMap.bottlenecks)をタグ表示するようにしました。人材プロフィールの全面的な再設計(「あなたが最も価値を発揮する企業」等の構造化フィールド)は未着手ですGET /api/company/history(診断履歴取得)とlib/scoreDelta.js(2時点の差分計算、テスト5件)。マイページから「過去の診断と比較する」を押すと、直近2回の診断結果を軸ごと・総合スコアで比較できます。診断が1回しかない場合は案内メッセージを表示します⑨ダッシュボード新設については、「進行中プロジェクト」「今月のKPI」「次回再診断日」等はプロジェクト管理機能に依存するため実装していません。それ以外の要素(総合スコア・Growth Map・課題TOP3)は、今回拡張した「マイページ」が実質的にその役割を担っているため、別ページとしては新設していません。
引き続き未着手: ②プロジェクト管理機能一式、③90日プラン自動生成、④AI月次レビュー、⑦人材プロフィールの全面再設計、⑩管理者画面の拡張。
「BATTER BOXを人材検索サイトではなく、企業診断を起点にしたサービスにする」という大規模なリニューアル依頼を受け、提示された34項目の優先順位(Priority 1〜5)のうち、Priority 1の一部に絞って実装しました。34項目すべてを一度に「動作確認済み」として提供することは、これまでこの開発全体で徹底してきた「検証していないコードを完成と言わない」姿勢に反するため、優先度の高い部分から確実に進める方針としています。
CompanySkillMap.topIssueDetailsを新設。最も深刻な3軸について、現状・放置した場合のリスク・優先度・推奨対応開始時期をAIが生成するようにした(lib/axes.jsのsanitizeTopIssueDetails()でAI応答の不正値を防御。テスト6件追加)lib/matchReason.jsが、良い点だけでなくミスマッチになり得る懸念点も一言添えるように拡張ご依頼の34項目のうち、以下は今回のセッションでは着手していません。特に②③④は、それぞれが単体でも数週間規模の開発になります。
① 新しい企業診断フォーム(組織別人数・部門ごとの5段階質問30〜50問の静的フォーム) — 現在は6ターンの対話形式のAI診断で、アーキテクチャが根本的に異なるため、置き換えるか併存させるかの設計判断が必要
② プロジェクト管理機能一式(/projects/[projectId]、タスク・KPI・稼働ログ・コメント・ミーティング履歴・AI進捗サマリー)
③ 90日実行プラン(BATTER BOX 90 DAYS PLAN)の自動生成
④ AI月次レビュー(AI Monthly Review)の自動生成
⑤ 再診断・Before/After比較機能
⑥ MATCH%の算出ロジックの多要素化(現状のscoreMatch()は軸の一致度ベース。業界経験・会社規模経験・役職経験等を個別に加点する設計への拡張)
⑦ 人材プロフィールの全面改善(「どんな会社の、どんな課題を解決できる人か」を構造化して表示、経験タグ、希望単価等)
⑧ 人材登録時のAIスキルマップの表示強化(現状も生成はしているが、専用ダッシュボードとしての見せ方は未着手)
⑨ 企業ダッシュボード・人材ダッシュボードの新設
⑩ 管理者画面の拡張(診断質問編集・診断スコア調整・人材推薦設定等)
⑪ 支援事例セクション(架空の事例を実績として見せることになるため、実際の導入事例が出てから実装することを推奨します)
⑫ FAQセクション
⑬ グローバルナビゲーションの状態別出し分け(ログイン前後・企業/人材別)
⑭ 新しいDBテーブル群(project_tasks, project_kpis, work_logs, monthly_reviews, rediagnoses等)
これらは、次のセッション以降で優先順位を相談しながら進めるのが現実的だと考えています。
「メッセージを送る」を押した際、単に空のメッセージ画面を開くのではなく、企業の課題内容(診断結果)と、なぜその人材が合うのかを踏まえて、AIが初回メッセージの文面を自動で下書きするようにしました。ユーザーは内容を確認し、必要なら編集してから送信ボタンを押すだけで送れます。
lib/messageDraft.js: generateOutreachMessage()。企業側から送る場合は企業視点、人材側から送る場合は人材視点で、一人称の異なる下書きを生成しますPOST /api/matches/connectが、初めての接続(まだメッセージが1件もないスレッド)の場合にのみ下書きを生成して返します。やり取りが既に始まっているスレッドを開き直した場合は下書きを生成しません(会話の邪魔をしないため)ユーザーが自分の情報を、AI診断/解析をやり直すことなく直接編集できるようにしました。ヘッダーの「設定」から入れます。
PATCH /api/company/profile、PATCH /api/talent/profile。会社名・事業ドメイン・従業員数等(人材側は氏名・役職・業種経験・実務経験年数・自己紹介)を直接更新します。AIによる再診断/再解析は行わず、レコードを直接書き換えるだけです。まだ一度も診断/解析を完了していないアカウントでは、この編集フォームの代わりに案内メッセージが表示されますPATCH /api/auth/account。変更には必ず現在のパスワードの入力が必要です。パスワードを変更した場合は、セキュリティのため全端末のセッションを失効させ、再ログインを求めます実際にデプロイして動作確認したところ、既にスキルマップを作成済みのアカウントが再ログインしても、毎回入力フォーム(ステップ1)から始まってしまう不具合が見つかりました。以下で修正しています。
GET /api/me/profile: ログイン中のアカウントに既存のスキルマップがあるかを返すDiagnosisSession/DiagnosisTurnはスキーマのみで書き込みが未実装でしたが、各質問・回答を保存するようにしました。/admin/companies/[id]で対話の全文を確認できますInvoice/Payoutモデルと発行UIは用意。B2B(企業→BeFree)側は、Stripe等の自動決済ではなく請求書のやり取りによる運用を前提とする方針で確定。BeFree→人材の支払いも含め、現状は手動でステータスを更新する運用)lib/mailer.jsのような形で連携する必要がある)「上場企業レベル」を見据えたフェーズ1として、以下を追加しました。
/forgot-password → /reset-password。PasswordResetToken(1時間有効・使い切り)を発行。メール送信は未連携のため、リンクは現状サーバーログにのみ出力されますlib/rateLimit.js。ログイン・新規登録・問い合わせフォームに適用(DBベースのため、サーバーレス環境でインスタンスをまたいでも機能します。高頻度トラフィックにはUpstash Redis等への置き換えを推奨)lib/errorLog.js。AI呼び出し・認証まわりの例外をErrorLogに記録し、/admin/audit-logで確認できます(外部監視サービスSentry等は未導入)lib/auditLog.js。管理画面でのお金・個人情報に関わる操作(マッチング記録、契約作成、請求/支払い、審査、問い合わせ対応)をすべてAuditLogに記録し、/admin/audit-logで確認できますtests/配下。Node.js組み込みのnode:testを使用(追加パッケージ不要)。npm testで実行できます。マッチングロジック(lib/matching.js)とパスワードハッシュ(lib/passwordHash.js)を対象に、このプロジェクト内で初めて実際に実行して合格を確認したテストです(18件、全件成功)上記はいずれも「最初の実ユーザーを迎える前にやっておきたい」水準のものです。CI/CD・権限の細分化・DBバックアップ体制・決済の自動化・J-SOX内部統制・外部監査などは、まだ先のフェーズとして残っています。
lib/matchReason.js)。一致度の高い軸に具体的に触れた一行を生成するようプロンプトで指示していますTalent.status(pending/approved/rejected)により、新規登録した実務経験者はデフォルトで「審査中」となり、管理画面(/admin/talents)で承認するまではマッチング候補に表示されません。承認/却下は一覧から1クリックで行えます。シードデータの3名は初期状態から承認済みです。
/ — マーケティング用ランディングページ(認証不要)。実際のアプリ本体は /app に移動しました/app — ユーザー向けアプリ本体(認証必須、旧/の内容)/company — 会社概要(https://befree-inc.com/about/ の内容を反映)/security — セキュリティに関する説明ページBeFree_営業用ピッチ資料.docx / BeFree_業務委託契約書ひな形.docx — 別途Claudeとのチャットで生成した営業訪問用資料・契約書ひな形(このリポジトリには含まれません)app/admin/layout.jsx で role=admin のログインを必須にしています。ログイン中はヘッダーに「ログアウト」ボタンが表示されます。
/admin — 登録件数のダッシュボード/admin/companies — 企業一覧・詳細(課題スキルマップ・記録済みの提案・マッチング候補)/admin/talents — 人材一覧・詳細(スキルマップ・記録済みの提案・マッチング候補)/admin/matches — 企業ごとの上位マッチング候補(その場でscoreMatch()を実行して表示)/admin/engagements — 契約(業務委託)一覧・ステータス変更・成果記録・請求/支払い管理(下記参照)支払いは「企業 → BeFree → 人材」の流れを前提としています。企業ユーザーと実務経験者ユーザーの間に直接の契約関係はなく、代わりに以下の2本の契約として扱います。
Invoice)Payout)Engagement(契約)は、この2本の金額(companyAmount / talentAmount)を保持し、その差額がBeFreeの取り分になります。標準手数料率はlib/pricing.jsのBEFREE_FEE_RATE(40%)で確定しており、「人材支払額 = 企業請求額 × 60%」が標準です。個別契約で例外的な料率にしたい場合は、金額を直接入力して調整できます。管理画面の「契約にする」フォームで両方の金額を入力し、/admin/engagementsでそれぞれ独立して請求・支払いのステータスを管理できます。契約書のひな形も、企業⇄BeFree用・BeFree⇄人材用の2種類を別途用意しています(このリポジトリには含まれません)。
/contact に公開の問い合わせフォームを設置しました(認証不要)。送信内容はInquiryとしてDBに保存され、/admin/inquiriesで確認・ステータス管理(未対応/返信済み/対応完了)ができます。外部のメール送信サービスとは連携していないため、新着の通知は現状「管理画面を見に行く」運用です。
企業/人材の詳細ページにある「提案として記録」ボタンでMatchレコードが作成され、「契約にする」で業務委託のEngagementに格上げされます。管理画面の操作はすべてServer Actionsで実装しており、各Actionの冒頭でrole=adminを再検証しています(ページの認証ガードを迂回されても安全なように)。
BeFree_技術構成設計.md の フェーズ4(成長予測エンジンの土台づくり、イベントログの活用)を参照してください。
以前は「データベースの初期設定」にターミナル操作(npx prisma db seed)が必要でしたが、v1.1で見直し、すべてブラウザの操作だけで公開できるようにしました。順番に進めてください。
このzipを展開し、中身(appフォルダやpackage.jsonなど)をGitHubリポジトリにアップロードしてください。GitHub上で空のリポジトリを作成し、「uploading an existing file」からドラッグ&ドロップでアップロードできます(ターミナル不要)。
既にリポジトリがある場合は、今回のzipの中身で上書きアップロードしてください(前回アップロードした時点よりもかなり機能が増えています)。
postgresql://...から始まる長い文字列)が発行されます。これをコピーしておいてください| Key | Value |
|---|---|
ANTHROPIC_API_KEY | Anthropicコンソールで取得したAPIキー |
DATABASE_URL | ②でコピーしたNeonの接続文字列 |
SETUP_KEY | 自分で決めた適当な英数字の文字列(合言葉。次の④で使います) |
SEED_ADMIN_EMAIL | 管理者としてログインしたいメールアドレス(任意。未設定でも動きます) |
SEED_ADMIN_PASSWORD | 管理者パスワード(任意。未設定だと自動生成されます) |
RESEND_API_KEY | メール通知(後述)を実際に送る場合のみ必要。未設定でも他の機能はすべて動きます |
EMAIL_FROM | 通知メールの送信元アドレス(任意。未設定時は[email protected]が使われます) |
デプロイ中に、データベースのテーブルが自動的に作られます(以前は別途コマンド実行が必要でしたが、ビルド時に自動実行されるようにしました)。
デプロイが完了したら、発行されたURLの末尾に /api/setup?key=あなたが決めた合言葉 を付けてブラウザで開いてください。
例: https://befree-app-xxxx.vercel.app/api/setup?key=your-secret-here
これだけで、管理者アカウントとサンプルデータ(候補人材3名・候補企業3社)が作成されます。SEED_ADMIN_PASSWORDを設定していなかった場合は、この画面にランダム生成されたパスワードが表示されるので、必ずその場でコピーして控えてください(二度と表示されません)。このURLは何度開いても安全です(既にセットアップ済みなら何もしません)。
https://befree-app-xxxx.vercel.app)を開く → ランディングページが表示されるはずです/login から、④で確認した管理者アカウントでログイン → /admin に入れるか確認これで公開は完了です。うまくいかない箇所があれば、どの手順で・どんな画面が出たかを教えてください。