SOUZOHSPECS

一般向けアプリ ・ HIREBASE

求人・スカウト

スカウトが届く、新しい求人体験のデモ

v2.0・作成 2026-08-13

デモを触る ↗MD

本番で使いたい方へデータの保存・セキュリティ・運用ルール・公開は、このデモには入っていません
  1. 要件定義書を保存する

    パソコンに hirebase-app のようなフォルダを作り、その中に保存します。

    要件定義書.md として保存

  2. ChatGPT で画面イメージを作る デザインにこだわりたい人

    ChatGPT に要件定義書を添付して、下の文を送ります。雰囲気のところは、好きな言葉に書きかえてください。

    ChatGPT に送る
    添付の要件定義書は、これから作るアプリ「求人・スカウト」のものです。
    このアプリのいちばん大事な画面を、デザイン画像にしてください。
    実際のアプリのように、文字やボタンまで入れてください。
    雰囲気:(例:やさしい/高級感/ポップ/シンプル など、好きな言葉で)

    気に入るまで「もっと明るく」「ボタンを大きく」のように直してもらい、できた画像を保存します。ほかの画面も欲しければ、同じように作ってもらえます。

  3. Claude Code に渡して「作って」と頼む

    Claude のデスクトップアプリで「Code」タブを開き、「Select folder」から hirebase-app を選びます。画面イメージがある人は、入力欄に画像をドラッグして貼りつけてから、下の文を送ります。詳しく

    Claude Code に送る(画像あり)
    @要件定義書.md をもとに、このアプリを作ってください。
    デザインは添付の画像に合わせてください。
    Claude Code に送る(画像なし)
    @要件定義書.md をもとに、このアプリを作ってください。

    作り終わるまで、しばらくかかります。途中で「許可しますか」と聞かれたら、内容を見て許可してください。

  4. 動かして確かめる・直す

    できたら、実際に画面を開いて触ります。直してほしいところは、スクリーンショットを貼りつけて、言葉で伝えれば大丈夫です。

    Claude Code に送る
    アプリを起動して、ブラウザで開けるようにしてください。
    Claude Code に送る
    (画面名)で(操作)をすると(今の動き)になります。
    (こうなってほしい動き)にしてください。
    Claude Code に送る
    このエラーを直してください。
    
    (ここにエラーの文をそのまま貼る)
  5. 途中で止まったら 必要なときだけ

    要件定義書が長いので、一度で作りきれないことがあります。そのときは新しい会話で、こう送ります。

    Claude Code に送る
    @要件定義書.md の続きを作ってください。
    まず、どこまでできているかを確かめてから進めてください。

    それでも進まないときは、30分無料相談でお手伝いします。公式LINEからのご相談もどうぞ。

    1段階ずつ頼みたいとき(全6段階)

    要件定義書には、作る順番が Phase 0〜5 で書かれています。止まりやすいときは、1つずつ頼むと確実です。

    Phase 0基盤とデザインシステム
    Claude Code に送る
    @要件定義書.md の「実装タスク」にある Phase 0 を作ってください。
    Phase 1求職者体験
    Claude Code に送る
    @要件定義書.md の「実装タスク」にある Phase 1 を作ってください。
    Phase 2企業体験
    Claude Code に送る
    @要件定義書.md の「実装タスク」にある Phase 2 を作ってください。
    Phase 3スカウトとメッセージ
    Claude Code に送る
    @要件定義書.md の「実装タスク」にある Phase 3 を作ってください。
    Phase 4運営・分析
    Claude Code に送る
    @要件定義書.md の「実装タスク」にある Phase 4 を作ってください。
    Phase 5仕上げ
    Claude Code に送る
    @要件定義書.md の「実装タスク」にある Phase 5 を作ってください。
  6. 本番で使うなら

    できたアプリはデモです。データの保存・セキュリティ・運用のルール・公開の仕方は入っていないので、そのまま公開すると誰でも中身を見られます。本番で使うのに何が要るかは、このページの下にまとめました。

    自社向けに作り変えるなら、まずこう聞いてみてください。

    Claude Code に送る
    この要件定義書は、記事に埋め込むデモ用に書かれています。
    自分の会社で実際に使えるように変えたいです。外したほうがいいところと、残したほうがいいところを教えてください。

    本番運用について相談する

準備や、うまくいかないときの対処は 作り方ガイド にまとめています。分からないときは 30分無料相談 へどうぞ。

これはデモ用の要件定義書です。本番で使うには、あと4つ必要です

MDでダウンロード

HIREBASE — 要件定義書 兼 Claude Code 実装指示書

版数 2.0(DBレス構成)/ 作成日 2026-08-13 / 発行 ソウゾウ合同会社 用途 「爆速アプリ開発」LP 実演デモ


このドキュメントの使い方(Claude Code へ)

これ1枚が、規約・仕様・タスクのすべて。リポジトリ直下に CLAUDE.md として置き、毎回読み込むこと。

  • 実装は「11. 実装タスク」の順に進める。フェーズを飛ばさない。
  • 各タスクの括弧内は要件ID。着手前に該当章を読むこと。
  • 仕様がここに書かれていない場合は、推測で実装せず質問する。
  • スコープ外(3章 Won't have)は、思いついても実装しない。
  • 「デモだから」を理由に品質を落とす判断はしない。

目次

  1. プロジェクト概要
  2. アーキテクチャ方針 — データベースを持たない
  3. スコープ定義
  4. ロールとデモ切替
  5. 画面一覧とユーザーフロー
  6. 機能要件
  7. データ設計
  8. デザイン要件
  9. 技術要件・ディレクトリ構成
  10. 非機能要件
  11. 実装タスク
  12. 受入基準

1. プロジェクト概要

背景と目的

「爆速アプリ開発」LPの訪問者は、価格(150万円)に対して「その金額で何がどこまで作れるのか」を判断できないままCTAに到達する。文章と静止画では、実装粒度・デザイン品質・機能密度が伝わらない。

そこで、LPから直接触れる実際に動作するプロダクトを用意し、「この水準のものが、この期間・この金額で出てくる」を体験として提示する。

目的 内容
実装力の証明 検索・応募・スカウト・メッセージ・分析まで、実サービス相当の機能密度を持つことを示す
デザイン品質の証明 テンプレート然としない、設計されたUIであることを1画面目で伝える
横展開の資産化 ドメイン差し替え(介護/特定技能/建設等)で、そのまま案件のベースにできる構造にする

プロダクト定義

項目 内容
プロダクト名 HIREBASE(ハイヤーベース)※仮称
一言定義 企業からのスカウトが届く、求人プラットフォーム
提供形態 レスポンシブWebアプリ。フロントエンド完結(サーバー側の永続化なし)
想定利用者 LP訪問者。誰でも即座に、登録なしで全ロールを体験できる
対応言語 日本語

プロダクトコンセプト

「検索する求人サイト」から「届く求人サイト」へ。

求職者は職務経歴を1度書けば、あとは企業からのスカウトを待つだけ。企業は条件に合う候補者を検索し、直接アプローチできる。応募・スカウトの両方向の導線を1つのメッセージ基盤に統合する。

デモとしての成功条件

  1. 触った瞬間に「作り込まれている」と分かる — 空の画面が1つもない。全画面にリアルなデータが入っている
  2. ロールを跨いだ体験が繋がる — 求職者として応募 → 企業に切り替えると、その応募が届いている
  3. 迷わない — 何ができるか、どこを見ればいいかが常に分かる
  4. リセットできる — 誰が触った後でも、初期状態に戻せる

2. アーキテクチャ方針 — データベースを持たない

基本方針

バックエンドとデータベースを持たない。すべてブラウザ内で完結させる。

一般的な構成 本プロジェクト
PostgreSQL / Supabase シードデータ(TypeScript)+ ブラウザ内ストア
認証(OTP・OAuth) デモ用ロール切替(ワンクリック)
Server Actions / API ストア上の同期的な操作
メール送信(Resend) アプリ内通知センターに配信されたことを表示
決済(Stripe) プラン選択UIのみ(購入完了までを画面上で再現)
ファイルアップロード ブラウザ内で Object URL 化して表示

なぜこの構成にするか

  • LPデモとして最適 — 訪問者は登録もログインもせずに、全機能を即座に触れる
  • 個人情報を一切預からない — 入力されたデータはブラウザから出ない。法令リスクとプライバシーポリシーの負担がゼロになる
  • 開発速度 — 認証・RLS・マイグレーション・メール基盤の実装が不要になり、UIと体験の作り込みに全時間を投下できる
  • 運用コストゼロ — Vercel の静的配信のみ。DBの月額も、バックアップも、障害対応も発生しない

差し替え可能性の担保(重要)

データアクセスは必ず lib/repo/ のリポジトリ層を経由すること。 コンポーネントからストアを直接触らない。

[ コンポーネント ]
        ↓  呼ぶのはこの層だけ
[ lib/repo/*.ts ]   ← 全メソッドを async にしておく
        ↓
[ lib/store/*.ts ]  ← Zustand + persist(localStorage)
        ↓
[ lib/seed/*.ts ]   ← 初期データ

リポジトリの各メソッドは、中身が同期処理でも async で定義し、await で呼ぶ。実案件へ転用する際、リポジトリの実装だけを Supabase 呼び出しに差し替えれば、UI層は一行も変更せずに済む。この構造自体が、営業時の説明材料になる。

// lib/repo/jobs.ts — 中身は同期でも async にする
export async function findJobs(query: JobQuery): Promise<JobSearchResult> {
  const jobs = useJobStore.getState().jobs
  return filterAndSort(jobs, query)   // 将来ここが supabase.from('jobs')... に変わるだけ
}

永続化の範囲

対象 挙動
シードデータ(求人・企業・候補者) 静的。読み込み専用
ユーザーの操作結果(応募・スカウト・メッセージ・ステータス変更・プロフィール編集) localStorage に保存。リロードしても保持される
ファイル添付 セッション内のみ(Object URL)。リロードでファイル名のみ残る
リセット ヘッダーの「デモをリセット」で localStorage をクリアし初期状態へ

擬似的な非同期を必ず入れること。 リポジトリの各操作に 200〜600ms のディレイを挟み、ローディング状態(スケルトン)を実際に見せる。即座に返ると、かえって作り物に見える。


3. スコープ定義

求職者向け機能

ID 機能 概要 優先度
F-C01 求人検索 フリーワード + ファセット絞り込み(職種/勤務地/雇用形態/年収/こだわり条件) Must
F-C02 求人詳細 労働条件の全項目表示、企業情報、マッチ度、類似求人 Must
F-C03 プロフィール/Web履歴書 基本情報、職務経歴、学歴、資格、スキルタグ、自己PR。完成度%表示 Must
F-C04 応募 内容確認、志望動機入力、完了通知 Must
F-C05 応募管理 応募履歴一覧、選考ステータスの可視化、辞退 Must
F-C06 メッセージ 企業とのスレッド型チャット、既読、ファイル添付 Must
F-C07 スカウト受信 スカウト一覧、興味あり/辞退、返信でメッセージ開始 Must
F-C08 お気に入り・保存検索 求人保存、検索条件保存、マッチ求人の表示 Should
F-C09 公開設定 スカウト受付ON/OFF、特定企業のブロック(現職企業対策) Should
F-C10 面接日程調整 企業提示の候補日から選択、確定でics発行 Should
F-C11 通知センター 種別フィルタ、既読管理 Must

企業向け機能

ID 機能 概要 優先度
F-E01 企業ダッシュボード 本日の応募、未読メッセージ、掲載中求人サマリ、要対応アラート Must
F-E02 求人作成・編集 6ステップウィザード。下書き保存、プレビュー、掲載申請、掲載期限設定 Must
F-E03 求人一覧 ステータス別タブ、複製、掲載期限アラート Must
F-E04 応募者管理(ATS) 選考ステータスのカンバンボード、D&D、担当者アサイン、評価、社内メモ Must
F-E05 候補者検索 スキル・経験年数・希望条件での検索。氏名等は非公開のまま表示 Must
F-E06 スカウト送信 テンプレート差し込み、残枠管理 Must
F-E07 メッセージ 候補者とのスレッド、定型文、担当者間の内部メモ Must
F-E08 面接日程調整 候補日の複数提示、確定通知 Should
F-E09 採用分析ダッシュボード 求人別ファネル、スカウト返信率、期間比較 Should
F-E10 企業ページ編集 ロゴ、カバー画像、事業紹介、社員の声、福利厚生 Should
F-E11 メンバー管理 メンバー一覧、権限表示・変更 Could
F-E12 プラン表示 掲載プランの比較と選択UI(決済は完了画面まで再現) Could

運営向け機能

ID 機能 概要 優先度
F-A01 求人審査 NGワード自動検出、目視確認、承認/差戻し(理由付き) Must
F-A02 運営ダッシュボード 審査待ち件数、通報件数、KPI推移グラフ Should
F-A03 通報対応 通報一覧、対象確認、非公開化 Should
F-A04 マスタ管理 職種、こだわり条件の一覧・並び替え Could

共通・基盤

ID 機能 概要 優先度
F-S01 ロール切替 求職者/企業/運営をワンクリックで切り替え。切替バーは常時表示 Must
F-S02 通知配信 アプリ内通知センターへの配信。メール送信はプレビュー表示で再現 Must
F-S03 デモリセット localStorage をクリアして初期状態に戻す Must
F-S04 ガイドツアー 初回訪問時に「何を試せるか」を3ステップで案内 Should

対象外(Won't have)

項目 理由
データベース・バックエンドAPI 2章の方針に基づく
本物の認証(OTP・OAuth・パスワード) ロール切替で代替
実際のメール送信・決済処理 UI上での再現に留める
ネイティブアプリ レスポンシブWebで対応
AIによる求人推薦 ルールベースのマッチ度%表示で代替
Web面接(ビデオ通話) 対象外
多言語UI 日本語のみ
複数ブラウザ間でのデータ共有 各ブラウザで独立。デモとして問題ない

スコープリスク: 「AIによる求人推薦」は要望として挙がりやすいが、精度の合意形成に時間がかかりスコープが膨張しやすい。スキルタグの一致数によるマッチ度%表示という説明可能なルールで実装し、体験としての「賢さ」を担保する。


4. ロールとデモ切替

ロール定義

ロールID 名称 見える画面
guest ゲスト(未ログイン想定) 公開画面のみ(トップ・検索・求人詳細・企業ページ)
candidate 求職者 公開画面 + マイページ一式
company 企業採用担当 企業管理画面一式
ops 運営 運営管理画面一式

認証は行わない。ロールはストアの状態として保持し、切替バーから即座に変更できる。

デモ切替バー(F-S01)

画面上部に常時表示する固定バー。以下を持つ。

  • 現在のロール表示と切替(4ロール)
  • 求職者ロール時:ペルソナ選択(例「佐藤 遥(Webディレクター・31歳)」)
  • 企業ロール時:所属企業の選択(シードの30社から)
  • 「デモをリセット」ボタン(確認ダイアログ付き)
  • 「これはデモです」の明示

デザイン上の扱い: このバーはプロダクト本体のUIではない。プロダクトのデザイントークンとは意図的に異なる扱い(ダークな帯 + 小さめのタイポ)にして、「アプリの外側にある操作パネル」であることを視覚的に区別する。プロダクト内に紛れ込ませない。

ロールを跨ぐ体験(デモの肝)

以下が繋がっていることが、このデモの説得力そのもの。必ず動くようにする。

  1. 求職者として求人に応募 → 企業に切替 → カンバンの「新規応募」にそのカードが増えている
  2. 企業として候補者にスカウト送信 → 求職者に切替 → スカウト一覧に届いている
  3. 求職者が「興味あり」 → 企業に切替 → メッセージスレッドが生成され、氏名が開示されている
  4. 企業が求人を掲載申請 → 運営に切替 → 審査待ちに並んでいる → 承認 → 求職者側の検索結果に出る

5. 画面一覧とユーザーフロー

全34画面。画面IDはディレクトリ構成と 1:1 で対応させる。

公開・求職者画面

画面ID 画面名 パス 主要要素
SC-001 トップページ / 検索バー、人気職種、注目求人、新着求人、サービス紹介
SC-002 求人検索結果 /jobs ファセットサイドバー、並び替え、求人カード一覧、ページネーション、条件保存
SC-003 求人詳細 /jobs/[id] 労働条件テーブル、企業情報、マッチ度、応募CTA(追従)、類似求人
SC-004 企業ページ /companies/[id] 企業概要、掲載中求人、社員の声、福利厚生
SC-005 職種別一覧 /jobs/category/[slug] 職種解説 + 該当求人一覧
SC-006 エリア別一覧 /jobs/area/[pref] 都道府県別求人一覧
SC-010 応募フォーム /jobs/[id]/apply 3ステップ(内容確認→志望動機→最終確認)、離脱防止ダイアログ
SC-020 マイページ /mypage プロフィール完成度、未読スカウト、選考中一覧、おすすめ求人
SC-021 プロフィール編集 /mypage/profile セクション別編集、自動保存、完成度メーター
SC-022 職務経歴編集 /mypage/careers 職歴の追加・並び替え・削除、在職中フラグ
SC-023 応募履歴 /mypage/applications ステータス別タブ、選考ステップ表示、辞退
SC-024 スカウト一覧 /mypage/scouts 未読/興味あり/辞退のタブ
SC-025 メッセージ一覧 /mypage/messages スレッド一覧、未読バッジ
SC-026 メッセージ詳細 /mypage/messages/[id] チャットUI、添付、日程調整カード、定型返信
SC-027 お気に入り /mypage/favorites 保存求人、保存検索条件
SC-028 通知センター /mypage/notifications 種別フィルタ、既読管理
SC-029 各種設定 /mypage/settings 通知設定、スカウト受付、ブロック企業

企業管理画面

画面ID 画面名 パス 主要要素
SC-100 企業ダッシュボード /admin 本日の応募、未読メッセージ、掲載中求人サマリ、要対応アラート
SC-101 企業ページ編集 /admin/company ロゴ・カバー画像、リッチテキスト、プレビュー
SC-102 メンバー管理 /admin/members メンバー一覧、権限変更
SC-110 求人一覧 /admin/jobs ステータス別タブ、複製、掲載期限アラート
SC-111 求人作成ウィザード /admin/jobs/new 6ステップ、自動下書き保存、必須項目ガイド、プレビュー
SC-112 求人編集 /admin/jobs/[id] 作成と同UI。公開中の編集は再審査対象を明示
SC-120 応募者カンバン /admin/applicants 6列、D&D、担当者アバター、絞り込み
SC-121 応募者詳細 /admin/applicants/[id] 履歴書ビュー、5段階評価、社内メモ、ステータス操作
SC-130 候補者検索 /admin/search スキル・経験年数・希望年収での絞り込み、匿名カード一覧
SC-131 候補者詳細(匿名) /admin/search/[id] 匿名プロフィール、スカウト送信ボタン
SC-132 スカウト作成 /admin/scouts/new テンプレート選択、差し込み変数、対象求人紐付け、残枠表示
SC-133 スカウト管理 /admin/scouts 送信履歴、開封率・返信率、テンプレート管理
SC-140 メッセージ /admin/messages 2ペイン、定型文、内部メモ
SC-150 分析ダッシュボード /admin/analytics 期間選択、求人別ファネル、スカウト効果
SC-160 プラン /admin/plan プラン比較、選択、完了画面(決済処理なし)

運営管理画面

画面ID 画面名 パス 主要要素
SC-200 運営ダッシュボード /ops 審査待ち件数、通報件数、KPI推移グラフ
SC-201 求人審査 /ops/jobs NG検出のハイライト、公開プレビュー、承認/差戻し
SC-202 通報管理 /ops/reports 通報一覧、対象コンテンツ、非公開化
SC-203 マスタ管理 /ops/masters 職種/こだわり条件の一覧・並び替え

主要フロー

フローA:求職者の応募

SC-001 トップ or 検索エンジン流入想定
   ↓
SC-002 求人検索 ── ファセットで絞り込み
   ↓
SC-003 求人詳細 ── 「応募する」
   ↓
SC-010 応募フォーム
   ├ Step1 送信内容の確認(プロフィール差分の編集可)
   ├ Step2 志望動機の入力
   └ Step3 最終確認 → 送信
   ↓
応募完了 ─┬→ 通知センターに「応募を受け付けました」
          ├→ 企業側の通知・ダッシュボード・カンバンに反映
          └→ SC-023 応募履歴に「応募済み」で追加

【体験の確認ポイント】ロール切替バーで企業に切り替えると、
カンバンの「新規応募」列に、今応募したカードが増えている。

フローB:企業のスカウト

SC-130 候補者検索 ── スキル「React」× 経験3年以上 × 東京
   ↓
SC-131 候補者詳細(匿名)── 氏名・現職企業は非表示
   ↓
SC-132 スカウト作成 ── テンプレ選択 + {{職種}}{{経験年数}}差し込み → 送信(残枠 -1)
   ↓
【ロール切替:求職者へ】
SC-024 スカウト一覧に着信 ─┬→「辞退」で終了
                          └→「興味あり」
   ↓
メッセージスレッド自動生成(この時点で企業側に氏名・連絡先が開示される)
   ↓
SC-026 / SC-140 メッセージ ── 企業が日程候補を3つ提示(日程調整カード)
   ↓
求職者が候補日を選択 → 確定 ─┬→ 双方の通知センターに配信 + ics ダウンロード
                             └→ SC-120 カンバンが「面接調整中」→「面接設定済」へ自動遷移

フローC:求人の掲載

SC-111 求人作成ウィザード
   Step1 基本情報(職種・雇用形態・タイトル)
   Step2 仕事内容(業務内容/変更の範囲)
   Step3 応募資格(必須・歓迎スキル)
   Step4 労働条件(勤務地/変更の範囲・時間・給与・休日・保険・受動喫煙対策)
   Step5 企業アピール(画像・社員の声)
   Step6 掲載設定(掲載期間・応募設定・担当者)
   ↓  ※各ステップで30秒ごと自動下書き保存
プレビュー(求職者から見える実画面)→ 掲載申請
   ↓
【ロール切替:運営へ】
SC-201 求人審査 ── NGワード自動検出(年齢制限/性別限定/必須項目の欠落)
   ├ 承認 → published → 求職者側の検索結果に出現
   └ 差戻し → 理由付きで企業の通知へ → SC-112 で修正 → 再申請

6. 機能要件

各要件は判定可能な粒度で記述している。テスト項目と 1:1 で対応させる。

6.1 ロール・デモ基盤

ID 要件 優先
FR-001 画面上部の固定バーから、4ロール(ゲスト/求職者/企業/運営)をワンクリックで切り替えられること。切替時に該当ロールのトップ画面へ遷移すること P1
FR-002 求職者ロールでは3名のペルソナから、企業ロールでは所属企業を選択できること P1
FR-003 「デモをリセット」で localStorage をクリアし、確認ダイアログを経て初期状態に戻ること P1
FR-004 操作結果はブラウザをリロードしても保持されること P1
FR-005 初回訪問時に3ステップのガイドツアーを表示し、「試せること」を案内すること。「今後表示しない」を選択できること P2
FR-006 リポジトリ層の各操作は 200〜600ms の擬似ディレイを持ち、その間スケルトンを表示すること P1
FR-007 ロールがアクセスできない画面(例:ゲストで /admin)にアクセスした場合、専用の案内画面から適切なロールへ切り替えられること。素の404を出さないこと P1

6.2 求人検索

ID 要件 優先
FR-101 フリーワード検索は、求人タイトル・業務内容・企業名・スキルタグを対象とすること。ひらがな/カタカナ/半角全角の表記ゆれを吸収すること P1
FR-102 ファセット絞り込みとして、職種(大分類→中分類)、勤務地(都道府県→市区町村)、雇用形態、年収レンジ、こだわり条件(リモート可/未経験歓迎/土日休み等)を提供すること P1
FR-103 各ファセット項目の横に、現在の絞り込み条件下でのヒット件数を表示すること。0件の項目は選択不可にすること P1
FR-104 絞り込み条件はURLクエリパラメータに反映し、ブラウザバック・URL共有・リロードで同一結果を再現できること P1
FR-105 並び替えは「新着順」「マッチ度順(求職者ロール時のみ)」「年収が高い順」を提供すること P1
FR-106 検索結果0件時、条件を1つ緩和した場合の件数を提示し、ワンタップで適用できること(例:「勤務地を『東京都』に広げると 124件」) P2
FR-107 求職者ロール時、各求人カードにマッチ度(%)を表示すること。算出は「希望条件との一致項目数 ÷ 評価項目数」とし、ホバー/タップで内訳を開示すること P2
FR-108 検索条件に名前を付けて保存でき、保存条件に合致する求人件数を SC-027 で確認できること P2
FR-109 検索結果は20件ずつ表示し、モバイルは無限スクロール、デスクトップはページネーションとすること P1
FR-110 適用中の絞り込み条件をチップで一覧表示し、個別に解除できること P1

6.3 求人詳細・応募

ID 要件 優先
FR-201 求人詳細に、労働条件の全項目を構造化テーブルで表示すること(7章の Job 型の必須項目すべて) P1
FR-202 モバイルでは応募CTAを画面下部に固定表示し、スクロール位置に関わらず常に応募できること P1
FR-203 ゲストロールで応募を試みた場合、求職者ロールへの切替を促し、切替後に元の応募フォームへ復帰すること P1
FR-204 応募フォームは3ステップで構成し、各ステップの入力内容を保持して、ブラウザバックでも消失しないこと P1
FR-205 入力途中でページを離脱しようとした場合、確認ダイアログを表示すること P2
FR-206 同一求人への重複応募を禁止し、応募済みの場合は応募ボタンを「応募済み」表示に置き換えること P1
FR-207 応募完了時、求職者の通知センターと企業側の通知・ダッシュボード・カンバンに即座に反映すること P1
FR-208 求職者は選考結果が出る前であれば応募を辞退でき、理由(任意)を送信できること P2
FR-209 求人詳細下部に、同一職種・同一エリアの類似求人を最大6件表示すること P2
FR-210 掲載終了した求人のURLは「この求人の募集は終了しました」+ 類似求人を表示すること P2

6.4 プロフィール・Web履歴書

ID 要件 優先
FR-301 プロフィールは「基本情報/職務経歴/学歴/資格・スキル/希望条件/自己PR」の6セクションで構成し、セクション単位で編集・保存できること P1
FR-302 入力状況に応じたプロフィール完成度(%)を常時表示し、未入力セクションへの導線を示すこと P1
FR-303 職務経歴は複数登録でき、ドラッグで並び替えられること。「現在も在籍中」を選択した場合、終了年月の入力を無効化すること P1
FR-304 スキルはタグ形式で登録し、入力時にマスタからのサジェストを表示すること。マスタにない場合もフリー入力を許可すること P1
FR-305 スカウト受付を ON/OFF でき、OFF時は候補者検索の結果に表示されないこと P1
FR-306 特定の企業を指定してブロックでき、ブロックした企業の候補者検索結果には表示されないこと(現職企業への露出防止) P1
FR-307 プロフィール編集は入力停止から2秒後に自動保存し、保存状態を画面上で示すこと P2

6.5 スカウト・メッセージ

ID 要件 優先
FR-401 候補者検索の結果および候補者詳細では、氏名・メールアドレス・電話番号・現職企業名・生年月日を表示しないこと。年齢は年代(30代等)で表示すること P1
FR-402 候補者検索の絞り込み条件に、性別・年齢・国籍を含めないこと P1
FR-403 スカウト本文にテンプレートを使用でき、{{職種}} {{経験年数}} {{スキル}} の差し込み変数を候補者データで置換できること P1
FR-404 スカウト送信時、企業プランの残枠を検証し、枠不足時は送信をブロックしてプラン画面への導線を表示すること P1
FR-405 同一候補者への同一企業からのスカウトは、前回送信から30日間は再送不可とすること P2
FR-406 求職者が「興味あり」を選択した時点で、当該企業に対してのみ氏名・連絡先を開示すること P1
FR-407 メッセージ送信はストア更新と同時に相手ロールのビューへ反映されること(ロール切替後に確認できること) P1
FR-408 既読状態を相手に表示すること。求職者側は既読表示のOFF設定を選べること P2
FR-409 メッセージにファイル(PDF/画像、1ファイル10MBまで、1メッセージ3件まで)を添付できること。セッション内で開けること P2
FR-410 企業側は同一スレッド内に、求職者に見えない内部メモを残せること。内部メモは背景色とラベルで明確に区別すること P2
FR-411 企業が最大5つの候補日時を提示し、求職者が1つを選択して確定できる日程調整カードをメッセージ内に挿入できること P2
FR-412 日程確定時、双方の通知センターに配信し、ics形式のカレンダーファイルをダウンロードできること P2
FR-413 メッセージ本文中の電話番号・メールアドレスらしき文字列を検出し、送信前に注意を表示すること P3

6.6 企業側 求人・応募者管理

ID 要件 優先
FR-501 求人作成は6ステップのウィザード形式とし、30秒ごとに下書きを自動保存すること P1
FR-502 労働条件の必須項目(7章 Job 型の required 項目)が未入力の場合、掲載申請できないこと。どの項目が不足しているかをステップ単位で示すこと P1
FR-503 固定残業代を「あり」にした場合、内訳と相当時間数の入力を必須化すること P1
FR-504 有期雇用を選択した場合、契約期間と更新上限の入力を必須化すること P1
FR-505 掲載申請前に、求職者から見える実際の画面をプレビューできること P1
FR-506 既存求人を複製して新規作成でき、複製時はステータスを draft とすること P1
FR-507 掲載期限が7日以内に迫った求人を、一覧とダッシュボードでアラート表示すること P2
FR-508 応募者管理はカンバン形式とし、列は「新規応募/書類選考中/面接調整中/面接設定済/内定/見送り」の6段階とすること P1
FR-509 カードのドラッグ&ドロップでステータスを変更でき、変更が即座に反映されること P1
FR-510 カンバンのステータス変更をキーボードのみでも行えること(カード選択 → 移動先選択) P1
FR-511 「見送り」へ移動した際、不採用通知の送信有無を選択でき、テンプレートを編集できること。送信するとメールプレビューが表示され、求職者側のステータスに反映されること P1
FR-512 応募者ごとに担当者をアサインでき、担当者で絞り込めること P2
FR-513 応募者詳細で5段階評価とコメントを記録でき、複数メンバーの評価を一覧できること P2
FR-514 分析ダッシュボードで、求人別の「表示数→応募数→書類通過→面接→内定」のファネルを表示し、期間を指定できること P2
FR-515 応募時点のプロフィールを凍結して保持し、応募後にプロフィールを編集しても、企業側に表示される応募内容が変わらないこと P1

6.7 運営・審査

ID 要件 優先
FR-601 求人審査時、以下を自動検出してハイライト表示すること:年齢制限を示す表現/性別を限定する表現/必須項目の欠落/給与の曖昧な表記 P1
FR-602 差戻し理由は定型選択(複数選択可)+ 自由記述で入力し、企業側の通知に反映すること P1
FR-603 審査は一覧画面から遷移せずにモーダルで完結でき、承認後は次の審査対象へ自動送りできること P2
FR-604 承認された求人が、求職者側の検索結果に即座に出現すること P1
FR-605 求人・企業ページに対する通報を受け付け、運営が対応状況(未対応/対応中/対応済)を管理できること P2
FR-606 運営ダッシュボードで、会員数・求人数・応募数・スカウト数の推移をグラフ表示すること P2

6.8 通知

ID 要件 優先
FR-701 通知センターに、応募受付・応募着信・スカウト受信・新着メッセージ・選考ステータス変更・日程確定・審査結果 の各種別が配信されること P1
FR-702 未読件数をヘッダーのバッジに表示し、種別でフィルタできること P1
FR-703 設定画面で通知種別ごとに ON/OFF でき、OFF にした種別は配信されないこと P2
FR-704 メール送信を伴う操作(応募受付・不採用通知等)では、実際に送られるメールのプレビューをモーダルで表示すること。デモであることを明示しつつ、実装の完成度を示す P2

7. データ設計

型定義(lib/types/

すべての型を先に定義し、これを唯一の真実とする。シードもストアもコンポーネントもこの型を参照する。

// ---- マスタ ----
type JobCategory = { id: string; parentId: string | null; name: string; slug: string; sortOrder: number }
type Prefecture  = { code: string; name: string; region: string }
type City        = { code: string; prefCode: string; name: string }
type JobFeature  = { id: string; name: string; icon: string }   // リモート可, 未経験歓迎 等
type Skill       = { id: string; name: string; categoryId: string }

// ---- 企業 ----
type Company = {
  id: string
  name: string
  logoUrl: string
  coverUrl: string
  industry: string
  employeeCount: number
  foundedYear: number
  address: string
  description: string          // リッチテキスト(サニタイズ済みHTML)
  benefits: string[]
  voices: { name: string; role: string; avatarUrl: string; body: string }[]
  planId: 'light' | 'standard' | 'premium'
  scoutQuota: number           // 月間スカウト枠
  scoutUsed: number
  members: CompanyMember[]
}
type CompanyMember = { id: string; name: string; avatarUrl: string; role: 'admin' | 'recruiter' | 'viewer' }

// ---- 求人 ----
type Job = {
  id: string
  companyId: string
  title: string                        // 60字まで
  categoryId: string
  employmentType: '正社員' | '契約社員' | 'パート・アルバイト' | '業務委託'
  // --- 労働条件(すべて必須。掲載申請のバリデーション対象) ---
  description: string                  // 業務内容(50字以上)
  descriptionScope: string             // 業務の変更の範囲
  workLocation: string                 // 就業場所
  workLocationScope: string            // 就業場所の変更の範囲
  prefCode: string
  cityCode: string
  salaryType: '年俸' | '月給' | '時給'
  salaryMin: number
  salaryMax: number                    // 上限も必須。片側のみは不可
  fixedOvertime: { enabled: boolean; amount?: number; hours?: number }  // enabled時は内訳必須
  workHours: string                    // 始業・終業・休憩・所定外労働の有無
  holidays: string
  insurances: ('健康保険' | '厚生年金' | '雇用保険' | '労災保険')[]
  smokingPolicy: string                // 受動喫煙防止措置
  contractPeriod: string               // 有期の場合は更新上限を含む。無期なら「期間の定めなし」
  trialPeriod: string                  // 試用期間の有無・期間・条件
  // --- 応募資格 ---
  requiredSkills: string[]
  welcomeSkills: string[]
  features: string[]                   // JobFeature.id[]
  // --- 掲載管理 ---
  status: 'draft' | 'under_review' | 'published' | 'rejected' | 'closed' | 'expired'
  rejectReason?: { codes: string[]; note: string }
  publishedAt?: string
  expiresAt?: string
  viewCount: number
  assigneeId?: string                  // 企業側担当者
  createdAt: string
  updatedAt: string
}

// ---- 候補者 ----
type Candidate = {
  id: string
  // 匿名ビューから除外される項目
  lastName: string; firstName: string
  lastNameKana: string; firstNameKana: string
  email: string
  phone: string
  birthDate: string
  currentCompanyName: string
  // 公開可能な項目
  avatarUrl: string
  prefCode: string
  currentCategoryId: string
  experienceYears: number
  currentSalary?: number
  desiredSalaryMin?: number
  desiredPrefCodes: string[]
  desiredEmploymentTypes: string[]
  selfPr: string                       // 3000字まで
  careers: Career[]
  educations: Education[]
  skills: { skillId: string; level: 1|2|3|4|5 }[]
  certifications: { name: string; acquiredAt: string }[]
  scoutEnabled: boolean
  blockedCompanyIds: string[]
  profileCompletion: number            // 算出値
}
type Career    = { id: string; companyName: string; role: string; from: string; to: string | null; isCurrent: boolean; body: string; sortOrder: number }
type Education = { id: string; schoolName: string; faculty: string; from: string; to: string }

// 候補者検索で使う匿名型。Candidate から機械的に導出する
type AnonymousCandidate = Omit<Candidate,
  'lastName'|'firstName'|'lastNameKana'|'firstNameKana'|'email'|'phone'|'birthDate'|'currentCompanyName'|'careers'
> & {
  ageDecade: 20 | 30 | 40 | 50          // 年代のみ
  careers: Omit<Career, 'companyName'>[] // 企業名を伏せた職歴
}

// ---- 応募・スカウト・メッセージ ----
type Application = {
  id: string
  jobId: string
  candidateId: string
  status: 'new' | 'screening' | 'scheduling' | 'scheduled' | 'offer' | 'rejected' | 'withdrawn'
  motivation: string
  profileSnapshot: Candidate           // 応募時点のプロフィールを凍結(FR-515)
  assigneeId?: string
  evaluations: { memberId: string; score: 1|2|3|4|5; comment: string; createdAt: string }[]
  internalNotes: { memberId: string; body: string; createdAt: string }[]
  threadId: string
  appliedAt: string
}

type Scout = {
  id: string
  companyId: string
  candidateId: string
  jobId: string
  body: string
  status: 'sent' | 'opened' | 'interested' | 'declined'
  threadId?: string                    // interested になった時点で生成
  sentAt: string
  openedAt?: string
}

type Thread = {
  id: string
  type: 'application' | 'scout'
  companyId: string
  candidateId: string
  jobId: string
  messages: Message[]
  lastReadByCandidate?: string
  lastReadByCompany?: string
}
type Message = {
  id: string
  senderType: 'candidate' | 'company' | 'system'
  senderId: string
  body: string
  attachments: { name: string; size: number; objectUrl: string }[]
  isInternalMemo: boolean              // trueなら求職者に表示しない
  interviewSlot?: InterviewSlot
  createdAt: string
}
type InterviewSlot = {
  id: string
  proposedAt: string[]                 // 最大5件
  selectedAt?: string
  status: 'proposed' | 'confirmed' | 'declined'
}

// ---- その他 ----
type Notification = {
  id: string
  targetRole: 'candidate' | 'company' | 'ops'
  targetId: string
  type: 'application_received'|'application_submitted'|'scout_received'|'message'|'status_changed'|'interview_confirmed'|'review_result'
  title: string
  body: string
  link: string
  emailPreview?: { subject: string; html: string }   // FR-704
  readAt?: string
  createdAt: string
}
type SavedSearch = { id: string; candidateId: string; name: string; query: JobQuery; createdAt: string }
type Report      = { id: string; targetType: 'job'|'company'; targetId: string; reason: string; status: 'open'|'in_progress'|'closed'; createdAt: string }

シードデータ(lib/seed/

ファイル 内容 件数
masters.ts 職種(大分類8 → 中分類60)、都道府県47、主要市区町村、こだわり条件20、スキル200
companies.ts 架空企業 30社
jobs.ts 求人(published 260 / draft 20 / under_review 10 / expired 10) 300件
candidates.ts 候補者(うち3名を操作用ペルソナとして詳細に作り込む) 200名
applications.ts 既存応募(各ステータスに分散配置) 60件
scouts.ts 既存スカウト(各ステータスに分散) 40件
threads.ts 会話履歴(自然な往復3〜10通) 30件
stats.ts 分析ダッシュボード用の日次データ(過去90日分)

シード作成のルール(品質を左右する)

  • 実在企業名・実在人名を使わない。 架空のものを使う
  • 画像はライセンス確認済みの素材のみ。人物写真は取得元を記録する
  • 求人票の本文はテンプレの繰り返しにしない。 職種ごとに書き分ける。ここが手抜きだとデモ全体が安っぽくなる
  • 年収・勤務地・スキルの分布に偏りを持たせる。均等分布は不自然
  • 日付は現在日時からの相対で生成する(3日前『2週間前』等)。固定日付を埋め込むと、時間が経つと古びる
  • 応募・スカウトのステータスは全種類を含める。どの画面を開いても空にしない

ストアとリポジトリ

lib/
├── types/          # 上記の型定義
├── seed/           # シードデータ
├── store/          # Zustand + persist(localStorage)
│   ├── session.ts  # 現在のロール、ペルソナ、企業
│   ├── data.ts     # jobs / candidates / applications / scouts / threads / notifications
│   └── ui.ts       # ガイドツアーの表示済みフラグ等
└── repo/           # ★ コンポーネントが触るのはここだけ
    ├── jobs.ts
    ├── candidates.ts
    ├── applications.ts
    ├── scouts.ts
    ├── threads.ts
    └── notifications.ts

リポジトリ層の規約(厳守)

  • 全メソッドを async で定義する。中身が同期でも例外なく
  • 200〜600ms の擬似ディレイを挟む(lib/repo/_delay.ts に共通実装)
  • 戻り値は { ok: true; data: T } | { ok: false; error: string } に統一する
  • コンポーネントから Zustand ストアを直接参照しない。 読み取りも書き込みもリポジトリ経由
  • 匿名化(CandidateAnonymousCandidate)は repo/candidates.ts の内部で行う。コンポーネントに Candidate 型を渡さない(FR-401)
// lib/repo/candidates.ts
export async function searchCandidates(q: CandidateQuery): Promise<Result<AnonymousCandidate[]>> {
  await delay()
  const { candidates } = useDataStore.getState()
  const companyId = useSessionStore.getState().companyId
  const visible = candidates.filter(c =>
    c.scoutEnabled && !c.blockedCompanyIds.includes(companyId)   // FR-305, FR-306
  )
  return { ok: true, data: filterAndSort(visible, q).map(toAnonymous) }  // 匿名化はここで
}

8. デザイン要件

アートディレクション

「書類の重さを、紙のような静けさで扱う」

求人・履歴書・選考は本質的に「書類」の世界。多くの求人サイトは情報量に負けて、彩度の高いバッジとカードが乱立する。HIREBASE は逆に、余白・罫線・タイポグラフィの階層だけで情報を整理し、色は意味のあるところにしか使わない。アクセントカラーは「今あなたが取れる行動」にのみ与える。

カラートークン

app/globals.css に CSS 変数で定義し、tailwind.config.ts の theme から参照する。直書きの色指定を禁止する。

:root {
  --ink-900: #131A24;   /* 見出し・本文 */
  --ink-600: #4A5566;   /* 補助テキスト */
  --ink-400: #8B94A3;   /* ラベル・非活性 */
  --brand-700: #1B4D3E; /* 主要CTA・ブランド */
  --brand-500: #2E7D64; /* ホバー・アクティブ */
  --brand-50:  #E8F0EC; /* 選択状態の背景 */
  --accent:    #C2703B; /* スカウト・注目のみ */
  --surface:   #FBFAF8; /* ページ背景 */
  --card:      #FFFFFF;
  --border:    #E6E3DC;
  --success: #2E7D64; --warning: #B4741C; --danger: #A83A3A; --info: #33608F;
}

配色ルール: アクセントカラー --accent1画面につき最大2箇所まで。「スカウトが届いている」「応募する」など、ユーザーが今取るべき行動にのみ使う。装飾目的の使用を禁止する。

タイポグラフィ

役割 書体 用途
ディスプレイ Zen Old Mincho 700 ページタイトル、求人タイトル、企業名。明朝体を主役に据えるのが視覚的差別化点
本文 Noto Sans JP 400/500 説明文、フォームラベル、UI全般
数値・ID Roboto Mono 500 年収、応募数、日付、求人ID
トークン デスクトップ モバイル 行間 用途
display 40px 28px 1.35 ページタイトル
h1 28px 22px 1.45 求人タイトル
h2 20px 18px 1.5 セクション見出し
h3 16px 15px 1.6 小見出し
body 15px 15px 1.85 本文(モバイルでも縮小しない)
sm 13px 13px 1.7 補助説明
caption 11px 11px 1.6 ラベル、注記

本文の1行あたり文字数は日本語で 36〜42文字に収まるようコンテンツ幅を制御する。

レイアウトとスペーシング

項目 定義
スペーシング 4pxベース:4 / 8 / 12 / 16 / 24 / 32 / 48 / 64 / 96
ブレークポイント sm 640 / md 768 / lg 1024 / xl 1280 / 2xl 1440
コンテンツ最大幅 1200px(管理画面は1440px)
グリッド デスクトップ12カラム/タブレット8/モバイル4。ガター24px
角丸 6px(カード・入力)/4px(バッジ)/999px(タグ・アバター)
2段階のみ。sm: 0 1px 2px rgba(19,26,36,.06) / md: 0 4px 16px rgba(19,26,36,.08)
罫線 1px #E6E3DC影よりも罫線でカードを区切ることを優先する

主要コンポーネント仕様

コンポーネント 仕様
求人カード 企業ロゴ40px/求人タイトル(2行クランプ)/企業名/年収(Mono)/勤務地/こだわり条件タグ最大3+「他N件」/マッチ度/保存アイコン
ボタン primary(brand-700塗り)/secondary(枠線)/ghost/danger の4種。高さ40px(sm 32 / lg 48)。タップ領域は最低44×44px
入力フィールド 高さ44px。ラベルは上部固定(プレースホルダをラベル代わりにしない)。エラーはフィールド直下に色+アイコン+文言。必須は「必須」バッジ(※印は使わない)
選考ステータスバッジ 6状態それぞれに固有色。色のみに依存せずテキストを併記
カンバンカード 候補者名/応募求人/経過日数/担当者アバター/評価星。ドラッグ中は3度傾け、影を md に引き上げる
チャットバブル 自分=brand-50背景・右寄せ/相手=白+罫線・左寄せ/内部メモ=warning系背景+「社内メモ」ラベル/システム=中央・グレー小文字
空状態 全一覧画面に専用の空状態。線画イラスト+「何が表示されるか」の説明+次の行動ボタン。「データがありません」単独表示は不可
スケルトン データ取得中はスピナーではなくスケルトン。レイアウトシフトを起こさない
デモ切替バー プロダクトのトークンとは意図的に別扱い(ダークな帯 + 小さめタイポ)。アプリの外側のパネルであることを視覚的に区別する

モーション

対象 duration 内容
ホバー・フォーカス 120ms ease-out。色と影のみ。移動は伴わない
モーダル・ドロワー 240ms cubic-bezier(.16,1,.3,1)。背後は8pxブラー+暗転
カンバンのドラッグ 180ms spring風。ドロップ時に着地のバウンド
ページ遷移 200ms フェード+8px上方向。検索結果の再取得時は行わない
検索結果の更新 150ms 件数のカウントアップのみ。カードの再アニメーションは行わない
トースト 200ms 下から出現。5秒で自動消滅(エラーは手動閉じのみ)

prefers-reduced-motion: reduce 時は全アニメーションを無効化する。スクロール連動アニメーションは使用しない — 求人検索は目的をもって使う画面であり、演出は操作の妨げになる。

アクセシビリティ(WCAG 2.1 AA)

ID 要件
A11Y-01 テキストと背景のコントラスト比は通常4.5:1以上、18px以上は3:1以上
A11Y-02 全ての操作をキーボードのみで完遂できる。カンバンのドラッグにはキーボード代替を用意(FR-510)
A11Y-03 フォーカスリングは2px・オフセット2pxで常時可視。outline:none の単独使用禁止
A11Y-04 情報を色のみで伝えない(ステータス = 色 + テキスト)
A11Y-05 全ての画像に代替テキスト。装飾画像は alt=""
A11Y-06 フォームの各入力に label を関連付け、エラーは aria-describedbyrole="alert" で読み上げる
A11Y-07 モーダルはフォーカストラップ + Esc で閉じる + 起動元へフォーカス復帰
A11Y-08 非同期の結果(検索件数の更新、保存完了)を aria-live で通知
A11Y-09 ページタイトルは画面ごとに固有。見出しレベルを飛ばさない
A11Y-10 ブラウザのフォントサイズ200%指定でも横スクロールを発生させず内容が読める

ライティング規約

原則
ボタンは「何が起きるか」 ○「応募内容を送信」/×「送信」「OK」
操作名は最後まで変えない 「掲載を申請」→ トーストも「掲載を申請しました」
エラーは原因と対処を書く ○「ファイルサイズが10MBを超えています。圧縮するか、別のファイルを選んでください」/×「アップロードに失敗しました」
謝罪しない ×「申し訳ございませんが〜」/○ 事実と次の行動
空状態は誘導する ○「まだスカウトは届いていません。プロフィールを充実させると、企業の目に留まりやすくなります」+ 編集ボタン
システム語を使わない ○「掲載を終了する」/×「ステータスをclosedに更新」

9. 技術要件・ディレクトリ構成

技術スタック(固定・勝手に変更しない)

レイヤ 技術 備考
フレームワーク Next.js 15(App Router)/ TypeScript strict
スタイリング Tailwind CSS + CSS Variables トークンは CSS 変数で定義
UIコンポーネント shadcn/ui(Radix UI基盤) a11y要件を自前実装せずに満たす
状態管理 Zustand + persist ミドルウェア localStorage に永続化
フォーム React Hook Form + Zod
D&D dnd-kit キーボード操作対応が必須のため。react-beautiful-dnd は使わない
グラフ Recharts 分析ダッシュボード
日付 date-fns(ja locale)
アイコン lucide-react
ホスティング Vercel
テスト Vitest / Playwright / axe-core

上記以外のライブラリを入れる前に必ず提案し、承認を得ること。

ディレクトリ構成

/
├── CLAUDE.md                  # このファイル
├── app/
│   ├── layout.tsx             # デモ切替バーをここに配置
│   ├── page.tsx               # SC-001
│   ├── jobs/
│   │   ├── page.tsx           # SC-002
│   │   ├── [id]/page.tsx      # SC-003
│   │   ├── [id]/apply/page.tsx# SC-010
│   │   ├── category/[slug]/page.tsx  # SC-005
│   │   └── area/[pref]/page.tsx      # SC-006
│   ├── companies/[id]/page.tsx# SC-004
│   ├── mypage/                # SC-020〜029
│   ├── admin/                 # SC-100〜160
│   ├── ops/                   # SC-200〜203
│   └── dev/components/        # コンポーネントカタログ(本番では非公開)
├── components/
│   ├── ui/                    # shadcn/ui プリミティブ
│   ├── domain/                # JobCard, ApplicantKanban, ChatThread, ConditionChips 等
│   ├── demo/                  # RoleSwitcher, ResetButton, GuideTour, EmailPreviewModal
│   └── layout/
├── lib/
│   ├── types/  seed/  store/  repo/   # 7章参照
│   ├── search/                # 検索・ファセット・マッチ度のロジック
│   ├── validation/            # Zod スキーマ
│   └── utils/
├── e2e/                       # Playwright
└── public/                    # 画像素材

コーディング規約

  • TypeScript strict。any 禁止。やむを得ない場合は unknown + 型ガード
  • ファイル名は kebab-case、コンポーネントは PascalCase
  • 1ファイル300行を超えたら分割を検討する
  • コンポーネントから Zustand ストアを直接参照しない。必ず lib/repo/ 経由
  • 検索・マッチ度・バリデーションなどのロジックは lib/ に置き、コンポーネントに埋め込まない(テスト対象にするため)
  • コミットは1タスクごと。メッセージに要件IDを含める 例:feat(jobs): 求人検索のファセット絞り込みを実装 (FR-102, FR-103)

10. 非機能要件

ID 要件 目標値
NFR-01 LCP(モバイル・4G相当) 2.5秒以下
NFR-02 INP(操作への応答) 200ms以下
NFR-03 CLS(レイアウトシフト) 0.1以下
NFR-04 初期JSバンドルサイズ(公開画面・gzip後) 200KB以下
NFR-05 求人300件に対する検索・絞り込みの体感遅延 擬似ディレイを除き即時
NFR-06 画像は WebP/AVIF、遅延読み込み
NFR-07 Lighthouse Performance 90 / Accessibility 95 / SEO 95 以上
NFR-08 対応ブラウザ Chrome / Safari / Edge 最新2バージョン、iOS Safari 16以降
NFR-09 全ページに固有の title / description / OGP画像
NFR-10 ユーザー入力はサニタイズし、リッチテキストは許可タグのホワイトリスト方式とする

実案件への転用時の注意(デモ本体の要件ではない)

将来この構成を実サービスへ転用する場合、以下が追加で必要になる。LP や商談でこの点に触れられた際に説明できるよう、記録として残す。

  • 求人票は職業安定法上の労働条件明示義務の対象。本デモの Job 型は明示必須項目を網羅済み(業務の変更の範囲・就業場所の変更の範囲・受動喫煙防止措置を含む)
  • 求職者情報を収集して企業に提供する場合、募集情報等提供事業者としての届出が必要になる可能性がある
  • 年齢制限・性別限定の禁止(労働施策総合推進法/均等法)。本デモは候補者検索の条件から性別・年齢・国籍を除外済み(FR-402)
  • 個人情報保護法に基づく利用目的の明示、開示・削除請求への対応
  • 本デモは個人情報をサーバーに送信しないため、これらの義務は発生しない。 ただし「デモである」旨は画面上に常時明示する

11. 実装タスク

この順に進める。フェーズを飛ばさない。 完了時は [x] に更新する。

Phase 0 — 基盤とデザインシステム(3日)

0-1. 初期化

  • Next.js 15 / TypeScript strict / App Router / Tailwind で初期化
  • ESLint / Prettier / husky(pre-commit で lint + typecheck)
  • 9章のディレクトリ構成を作成

0-2. 型とシード

  • lib/types/ に7章の型定義をすべて実装
  • lib/seed/masters.ts(職種8→60、都道府県47、市区町村、こだわり条件20、スキル200)
  • lib/seed/companies.ts(架空30社。ロゴ・カバー画像含む)
  • lib/seed/jobs.ts(300件。職種ごとに本文を書き分ける。テンプレの使い回し禁止
  • lib/seed/candidates.ts(200名。うち3名は操作用ペルソナとして詳細に作り込む)
  • lib/seed/{applications,scouts,threads,stats}.ts(各ステータスに分散配置)
  • 日付は現在日時からの相対で生成する

0-3. ストアとリポジトリ

  • lib/store/{session,data,ui}.ts を Zustand + persist で実装
  • lib/repo/ の全モジュールを実装。全メソッド async、擬似ディレイ、Result型で統一
  • toAnonymous()repo/candidates.ts 内に実装(FR-401)
  • リポジトリ層の単体テストを書く(この時点で書く。後回しにしない)

0-4. デザインシステム

  • app/globals.css にカラートークンを CSS 変数で定義
  • tailwind.config.ts から CSS 変数を参照するよう theme を拡張(スペーシング・角丸・影・ブレークポイント)
  • フォント(Zen Old Mincho / Noto Sans JP / Roboto Mono)を next/font で最適化
  • タイプスケールをユーティリティクラスとして定義
  • shadcn/ui を導入し、トークンに合わせて Button / Input / Select / Checkbox / Radio / Dialog / Sheet / Toast / Badge / Tabs / Tooltip / DropdownMenu を上書き
  • 共通コンポーネント:EmptyState / Skeleton / PageHeader / Pagination / FileUploader / TagInput / StatusBadge / ConditionChips
  • prefers-reduced-motion の全体対応
  • /dev/components にコンポーネントカタログを作成し、全バリアントを一覧できるようにする

0-5. デモ基盤

  • RoleSwitcher(4ロール切替、ペルソナ選択、企業選択)(FR-001, FR-002)
  • デモ切替バーを app/layout.tsx に固定配置。プロダクトのトークンとは別扱いのデザイン
  • 「デモをリセット」(確認ダイアログ + localStorage クリア)(FR-003)
  • ロール権限外アクセス時の案内画面(素の404を出さない)(FR-007)

Phase 0 完了チェック

  • /dev/components で全コンポーネントがトークン通りに表示される
  • ロール切替が動作し、リロードしても状態が保持される
  • リポジトリ層のテストが通る

Phase 1 — 求職者体験(4日)

1-1. 求人検索

  • lib/search/ に検索・ファセット・マッチ度のロジックを実装(テスト付き)
  • 表記ゆれ吸収(ひらがな/カタカナ/半角全角)(FR-101)
  • SC-002 検索結果。URLクエリと状態を完全同期 (FR-104)
  • ファセットサイドバー。ヒット件数表示、0件は選択不可 (FR-102, FR-103)
  • 適用条件のチップ表示と個別解除 (FR-110)
  • 並び替え3種 (FR-105)
  • モバイル無限スクロール/デスクトップページネーション (FR-109)
  • 0件時の条件緩和サジェスト (FR-106)
  • マッチ度計算と内訳表示 (FR-107)
  • JobCard コンポーネント(8章の仕様に準拠)

1-2. 求人詳細

  • SC-003 求人詳細。労働条件の全項目を構造化テーブルで表示 (FR-201)
  • モバイル固定応募CTA (FR-202)
  • 類似求人 最大6件 (FR-209)
  • 掲載終了求人の表示 (FR-210)
  • SC-004 企業ページ
  • SC-005 職種別 / SC-006 エリア別一覧

1-3. プロフィール

  • SC-021 プロフィール編集(6セクション、セクション単位保存)(FR-301)
  • 完成度%の算出と未入力セクションへの導線 (FR-302)
  • SC-022 職務経歴(D&D並び替え、在職中フラグで終了年月を無効化)(FR-303)
  • スキルタグ入力(マスタサジェスト + フリー入力)(FR-304)
  • 2秒後の自動保存と保存状態表示 (FR-307)
  • SC-029 設定:スカウト受付 ON/OFF (FR-305)、企業ブロック (FR-306)

1-4. 応募

  • SC-010 応募フォーム3ステップ。入力内容を保持 (FR-204)
  • 離脱確認ダイアログ (FR-205)
  • repo/applications.submit():重複チェック → profileSnapshot 凍結 → 通知配信 (FR-206, FR-207, FR-515)
  • ゲストロールからの応募時のロール切替誘導と復帰 (FR-203)
  • SC-023 応募履歴(ステータス別タブ、選考ステップ、辞退)(FR-208)
  • SC-020 マイページ
  • SC-027 お気に入り・保存検索 (FR-108)
  • SC-028 通知センター (FR-701, FR-702)

1-5. トップ

  • SC-001 トップページ

Phase 1 完了チェック

  • フローAが完走する(検索 → 詳細 → 応募 → 応募履歴に反映)
  • リロードしても応募が残っている
  • どの画面にも空の状態がない

Phase 2 — 企業体験(4日)

2-1. 求人管理

  • SC-111 求人作成ウィザード6ステップ、30秒ごと自動下書き保存 (FR-501)
  • 必須項目バリデーション。未入力では掲載申請不可。不足項目をステップ単位で提示 (FR-502)
  • 固定残業代 ON で内訳・時間数を必須化 (FR-503)
  • 有期雇用選択時に契約期間・更新上限を必須化 (FR-504)
  • 求職者視点のプレビュー (FR-505)
  • SC-110 求人一覧(ステータス別タブ、複製)(FR-506)
  • 掲載期限アラート (FR-507)
  • SC-112 求人編集。公開中の編集は再審査対象であることを明示

2-2. 応募者管理(ATS)

  • SC-120 応募者カンバン6列 (FR-508)
  • dnd-kit による D&D ステータス変更 (FR-509)
  • キーボードでのステータス変更代替 (FR-510, A11Y-02)
  • 「見送り」移動時の不採用通知選択 + テンプレート編集 + メールプレビュー (FR-511, FR-704)
  • SC-121 応募者詳細(履歴書ビュー、5段階評価、社内メモ)(FR-513)
  • 担当者アサインと絞り込み (FR-512)

2-3. ダッシュボード・企業ページ

  • SC-100 企業ダッシュボード
  • SC-101 企業ページ編集(画像アップロード、リッチテキストはサニタイズ)(NFR-10)
  • SC-102 メンバー管理
  • SC-160 プラン画面

Phase 2 完了チェック

  • フローCの前半(求人作成 → 掲載申請)が完走する
  • 必須項目が1つでも欠けていると掲載申請できない
  • 求職者ロールで応募 → 企業ロールに切替 → カンバンにそのカードがある
  • カンバンがキーボードのみで操作できる

Phase 3 — スカウトとメッセージ(3日)

3-1. 候補者検索・スカウト

  • SC-130 候補者検索。AnonymousCandidate のみを扱う (FR-401)
  • 絞り込み条件に性別・年齢・国籍を含めない (FR-402)
  • SC-131 候補者詳細(匿名)
  • SC-132 スカウト作成。テンプレート + 差し込み変数の置換 (FR-403)
  • repo/scouts.send():残枠検証 (FR-404)、30日再送制限 (FR-405)
  • SC-024 スカウト一覧(求職者側)。「興味あり」でスレッド生成 + 氏名開示 (FR-406)
  • SC-133 スカウト管理(開封率・返信率、テンプレート管理)

3-2. メッセージ

  • SC-025 / SC-026 求職者側メッセージ
  • SC-140 企業側メッセージ(2ペイン)
  • チャットバブル4種(自分/相手/内部メモ/システム)(FR-410)
  • 既読表示と求職者側の OFF 設定 (FR-408)
  • ファイル添付(Object URL、10MB×3件まで)(FR-409)
  • 連絡先らしき文字列の検出と注意表示 (FR-413)
  • ロール切替でメッセージが双方向に反映されることを確認 (FR-407)

3-3. 日程調整

  • 日程調整カード。最大5候補提示 → 1つ選択 (FR-411)
  • 確定時に双方の通知センターへ配信 + ics ダウンロード (FR-412)
  • 確定でカンバンのステータスを自動遷移

Phase 3 完了チェック

  • フローBが完走する(候補者検索 → スカウト → 興味あり → メッセージ → 日程確定)
  • 「興味あり」より前に、企業側のどの画面からも候補者の氏名が見えないことを確認する

Phase 4 — 運営・分析(2日)

  • NGワード検出エンジン(年齢制限・性別限定・必須項目欠落・曖昧な給与表記)(FR-601)
  • SC-201 求人審査(検出結果ハイライト、モーダル完結、次へ自動送り)(FR-603)
  • 差戻し理由の定型選択 + 自由記述 → 企業の通知へ (FR-602)
  • 承認 → 求職者側の検索結果に即時反映 (FR-604)
  • SC-202 通報管理 (FR-605)
  • SC-203 マスタ管理
  • SC-200 運営ダッシュボード(Recharts で推移グラフ)(FR-606)
  • SC-150 企業向け分析ダッシュボード(ファネル、期間指定)(FR-514)
  • 通知種別ごとの ON/OFF (FR-703)

Phase 4 完了チェック

  • フローCが完走する(求人作成 → 審査 → 承認 → 検索結果に出現)

Phase 5 — 仕上げ(3日)

5-1. 体験の総点検

  • 4章「ロールを跨ぐ体験」の4パターンをすべて手動で確認する
  • 全34画面を開き、空の画面が1つもないことを確認する
  • 全画面の空状態・ローディング・エラー状態を確認する
  • ガイドツアーを実装 (FR-005)

5-2. アクセシビリティ監査

  • axe-core を全画面に実行し、Critical / Serious を0件に
  • キーボードのみで全画面を操作完遂する手動確認
  • フォントサイズ200%で横スクロールが出ないことを確認 (A11Y-10)

5-3. パフォーマンス・SEO

  • 画像を WebP/AVIF + 遅延読み込み (NFR-06)
  • 初期JSバンドルを200KB以下に削減 (NFR-04)
  • 全ページに固有の title / description / OGP画像 (NFR-09)
  • Lighthouse で Performance 90 / Accessibility 95 / SEO 95 を達成 (NFR-07)

5-4. テストと公開

  • Playwright で フローA・B・C の E2E を実装・実行
  • Vitest:検索・マッチ度・バリデーション・リポジトリのカバレッジ80%以上
  • /dev/components を本番で非公開に
  • Vercel へデプロイ
  • LP への導線設置
  • 各フェーズのキャプチャを整理し、LP掲載用の時系列コンテンツにまとめる

合計 16営業日(約3週間)/1名専任 + レビュー体制。 各フェーズ完了時点でプレビューURLと画面キャプチャを残し、「Day 3 でここまで」「Day 7 でここまで」という時系列コンテンツとして LP に掲載する。爆速であることを、主張ではなく記録で示す。


12. 受入基準

  1. 6章の優先度 P1 の要件がすべて実装されていること
  2. 5章の主要フローA・B・Cが、エンドツーエンドで完走すること
  3. 4章「ロールを跨ぐ体験」の4パターンがすべて動作すること
  4. 「興味あり」より前に、企業側から候補者の氏名・連絡先がどの経路からも見えないこと
  5. 労働条件の必須項目が未入力の求人は、掲載申請できないこと
  6. 全34画面のいずれにも空の状態がなく、リアルなデータが表示されていること
  7. リロード後も操作結果が保持され、「デモをリセット」で初期状態に戻ること
  8. axe-core で Critical / Serious の指摘が0件であること
  9. Lighthouse の基準値を全公開画面で満たすこと
  10. データアクセスがすべて lib/repo/ を経由しており、コンポーネントが Zustand ストアを直接参照していないこと

判断に迷ったときのルール

  1. 仕様がこの文書にない場合は、実装せずに質問する。 推測で作らない
  2. アクセシビリティと「空の画面を作らない」は削減対象外。 速度を出すために削らない
  3. スコープ外(3章 Won't have)は、思いついても実装しない
  4. 「デモだから」を理由に品質を落とす判断はしない
  5. コンポーネントからストアを直接触りたくなったら、それはリポジトリ層に足りないメソッドがあるということ。リポジトリに追加する

FROM DEMO TO PRODUCTION

本番で使うには、あと4つ必要です

この要件定義書は、画面と動きを確かめるためのデモとして書かれています。「求人・スカウト」を社内やお客さまに実際に使ってもらうには、見た目の裏側に次の4つが要ります。

  1. 1データの保存

    デモでは
    データはその端末のブラウザの中にしか残りません。ほかの端末やほかの人とは共有されず、消えることもあります。
    本番では
    サーバーのデータベースに保存し、バックアップと復元ができるようにします。
  2. 2セキュリティ

    デモでは
    ログインがなく、誰でもすべての画面とデータを見られます。
    本番では
    ログイン、権限(誰が何を見られるか)、通信とデータの暗号化、不正なアクセスへの対策を入れます。
  3. 3ガバナンス(運用ルール)

    デモでは
    決めていません。
    本番では
    誰がいつ何をしたかの記録、個人情報の扱い(プライバシーポリシー・同意)、アカウントの発行と削除、障害のときの連絡体制を決めます。
  4. 4公開・デプロイ

    デモでは
    手元で動かすか、デモとして公開するだけです。
    本番では
    独自ドメイン、本番とテストの環境分け、更新の手順、監視と障害対応、月々の費用の管理を整えます。

本番運用をお考えの方へ

ぜひ一度ご相談ください。最適なプランのご紹介と、進め方をお伝えします。代表の西澤が直接お話しします。

  • 自分で作ったアプリを、そのまま本番で使いたい
  • どこまで自分で作り、どこから任せるか決めたい
  • 社内の決まりやセキュリティの基準に合わせたい

下のカレンダーから、そのまま日程を選べます。

カレンダーが表示されないときは こちらから日程を選べます。日程を決める前に聞きたいことがあれば、公式LINEお問い合わせフォーム からどうぞ。

FREE CONSULTATION

分からないときは、お気軽に30分無料相談へ

途中で止まった、エラーが消えない、自社向けに作り変えたい。どんなことでも大丈夫です。

日程を決めて話す

代表の西澤と直接お話しできます。空いている日時を選ぶだけで予約できます。

代表 西澤と話す日程を選ぶ

まずは問い合わせる

要件定義書がほしい方・ご相談は公式LINEから。メールでのお問い合わせはフォームから。