TSUMU — 要件定義書 兼 Claude Code 実装指示書
版数 1.0(DBレス構成 / 記事埋め込み型デモ)/ 作成日 2026-08-24 / 発行 ソウゾウ合同会社 用途 SEO記事「【デモで試せる】Claude Codeでタスク管理システムを自作する方法と費用|動画つき資料【2026年】」に組み込む実演デモ
このドキュメントの使い方(Claude Code へ)
これ1枚が、規約・仕様・タスクのすべて。リポジトリ直下に CLAUDE.md として置き、毎回読み込むこと。
- 実装は「12. 実装タスク」の順に進める。フェーズを飛ばさない。
- 各タスクの括弧内は要件ID。着手前に該当章を読むこと。
- 仕様がここに書かれていない場合は、推測で実装せず質問する。
- スコープ外(3章 Won't have)は、思いついても実装しない。
- 「デモだから」を理由に品質を落とす判断はしない。
- この領域だけの特別ルール:既製ツールの機能を真似しない。 タスク管理は世界中に優れた既製品がある。それを模倣した瞬間、劣化コピーになる。 自作の理由(1章)に紐づかない機能は実装しない。
姉妹プロジェクトとの関係 HIREBASE/RELATE/CASTA/FIELDPIN/KANADE/MIWAKE/KAMIWAZA/KURA/TOKIWA/HIBI/TENKEN に続くシリーズ。並べて「同じテンプレ」と思われた時点で失敗。 特に RELATE / KURA / TOKIWA との差別化に注意。 RELATE=罫線と等幅数字で表を読ませる(藍)/ KURA=各行に在庫の水位バー(ティール+アンバー)/ TOKIWA=各行に24時間帯(インディゴ+コーラル)/ HIBI=1カラム・広い余白・15px本文(墨+若草)/ TENKEN=濃い地に巨大な判定ボタン(安全色)/ TSUMU=カードが主役。角丸12pxの厚みのあるカードが、ボード・リスト・カレンダーの3つのビューを行き来する。(生成りの紙色+インディゴ、ドラッグの手触りが命) TSUMU だけが「同じデータを3つの見え方で往復する」アプリ。 8章参照。
目次
- プロジェクト概要
- アーキテクチャ方針
- スコープ定義
- ロールとデモ切替
- 画面一覧とユーザーフロー
- 機能要件
- データ設計
- デザイン要件
- 技術要件・ディレクトリ構成
- 非機能要件
- 費用設計
- 実装タスク
- 受入基準
1. プロジェクト概要
背景と目的
タスク管理システムは、世界一競合が多い領域である。無料で使える優れたツールが山ほどある。
それでも毎年、自作の相談が来る。 そして相談者は決まってこう言う。
「いろいろ試したんですが、うちのやり方に合わなくて」
この領域には、最初に共有すべき事実が3つある。
事実①:タスク管理ツールが定着しない原因は、ツールではなく「入力されないこと」
どんなに優れたツールでも、タスクが入っていなければ何も管理できない。 そして現実には、タスクは次の場所に散らばっている。
| 散らばる場所 | 実態 |
|---|---|
| チャット(Slack / Teams / LINE) | 「これお願いします」で流れていく |
| メール | 依頼が本文に埋もれている |
| 会議の議事録 | 決まったのに誰も転記しない |
| 頭の中・付箋・手帳 | そもそも共有されていない |
タスク管理ツールへの入力は「二重作業」に感じられる。 だから入力されない。
タスク管理システムの成否は、機能ではなく「タスクがどれだけ楽に入るか」で決まる。 目標は 依頼から3秒でタスクになること。
事実②:「タスク」は3つの別のものが混ざっている
多くのタスク管理が破綻するのは、性質の違うものを1つのリストに入れるから。
| 種類 | 例 | 必要な扱い |
|---|---|---|
| ① すぐ終わる作業 | 「請求書を送る」「返信する」 | リストで十分。 期限と完了チェックだけ |
| ② 段取りが要る仕事 | 「新サービスの立ち上げ」 | 分解と順序。 親子関係、依存、進捗 |
| ③ 繰り返し | 「毎月の請求処理」「週次report」 | テンプレートと自動生成。 毎回作るのは無駄 |
①だけならメモアプリで足りる。②が必要になった瞬間に「システム」が要る。③を手作業でやっている会社は、そこだけで週に何時間も溶かしている。
TSUMU はこの3つを別の機能ではなく、同じタスクの見え方の違いとして扱う。
事実③:同じタスクを、人によって違う見え方で使いたい
- 現場担当は**「自分の今日やること」のリスト**が見たい
- リーダーは**「誰が何を持っているか」のボード**が見たい
- 管理者は**「いつ終わるのか」のカレンダー/ガント**が見たい
別々のツールを使うと、データが分かれて死ぬ。 同じデータを、3つの見え方で往復できることが要件になる。
TSUMU はこの3つの事実を前提に設計する。
| 目的 | 内容 |
|---|---|
| 実装力の証明 | ボード/リスト/カレンダーの3ビュー同期、ドラッグ&ドロップ、繰り返し、依存関係、通知まで、実運用に耐える構造を見せる |
| 期待値の正常化 | **「ツールではなく、入力されないことが問題」**を実物で示す。入力にかかる秒数を可視化する |
| 自作の理由の言語化 | **「既製ツールで足りるなら使うべき」**を正直に書き、自作が正当化される条件を示す(11章) |
| 費用判断の材料 | 無料枠の既製ツールがある中での損益分岐を示す |
プロダクト定義
| 項目 | 内容 |
|---|---|
| プロダクト名 | TSUMU(積む)※仮称 |
| 一言定義 | 依頼を3秒でタスクにし、ボード・リスト・カレンダーで往復できるタスク管理システム |
| 想定業態 | 中小企業の受託業務、士業、制作・開発チーム、施工管理、店舗運営、バックオフィス |
| 提供形態 | レスポンシブWebアプリ(PWA)。フロントエンド完結(サーバー側の永続化なし) |
| 主戦場 | PCが主(ボード・カレンダーはPCで真価を発揮)。スマホは「見る」と「素早く足す」に特化 |
| 想定利用者 | 記事の読者。登録なしで、担当者側と管理者側の両方を体験できる |
プロダクトコンセプト
「3秒で積む。3つの見え方で捌く。」
TSUMU はタスクの追加にかかる秒数を計測し、表示する。1行の自然文から期限・担当・案件を切り出し、
⌘Kからどこでも積める。入れるのが速いから、入る。 そして積まれたタスクは、ボード・リスト・カレンダーを自由に往復する。
デモとしての成功条件
- 入力が速いことが分かる — 1行入力から期限・担当が自動で切り出され、追加までの秒数が出る
- 3ビューが同期する — ボードで動かすとカレンダーが変わり、リストで期限を変えるとボードの列が変わる
- ドラッグの手触りが良い — ボードのD&Dが引っかからない。ここが悪いと全部が安っぽく見える
- 繰り返しが自動生成される — 完了すると次回分が自動で積まれる
- 依存関係が見える — 前工程が終わらないと着手できないタスクが分かる
- ロールで見え方が変わる — 担当者は自分の今日、リーダーはチームのボード
- リセットできる — 誰が触った後でも初期状態に戻せる
2. アーキテクチャ方針
基本方針
バックエンドとデータベースを持たない。すべてブラウザ内で完結させる。
| 一般的な構成 | 本プロジェクト |
|---|---|
| PostgreSQL | シードデータ + ブラウザ内ストア |
| 認証・SSO | デモ用ロール切替(ワンクリック) |
| REST / GraphQL API | ストア上の同期的な操作 |
| リアルタイム同期(WebSocket) | ロール切替時に反映される形で再現 |
| チャット連携(Slack / Teams) | 「取り込みシミュレーション」で再現(2章) |
| メール・チャット通知 | 送信内容のプレビュー表示で再現 |
| 自然文からのタスク生成 | これは本物。 ルールベースで実際に解析する |
| 繰り返しタスクの生成 | これも本物。 完了時に次回分を実際に作る |
| 3ビューの同期 | これも本物。 同一データを3つの見え方で表示する |
なぜこの構成にするか
- LPデモとして最適 — 訪問者は登録もログインもせずに、全機能を即座に触れる
- タスクの中身が外に出ない — 業務内容は機密。データを預からないことが訴求になる
- 開発速度 — 認証・APIの実装が不要になり、入力速度とドラッグの手触りに全時間を投下できる
- 運用コストゼロ — Vercel の静的配信のみ
3ビュー同期の原則(最重要の設計判断)
ボード・リスト・カレンダーは「3つの機能」ではなく、「1つのデータの3つの射影」である。
[ Task(唯一のデータ)]
│
┌───────────────────┼───────────────────┐
▼ ▼ ▼
[ ボード ] [ リスト ] [ カレンダー ]
status で列分け 条件で絞り込み dueDate で配置
│ │ │
列を移動 → 期限を変更 → 日付をドラッグ →
status を更新 dueDate を更新 dueDate を更新
└───────────────────┴───────────────────┘
▼
★ すべて同じ Task を更新する
★ 他のビューに即座に反映される
| ID | 規約 |
|---|---|
| VIEW-01 | ビューごとに別のデータを持たないこと。 3ビューは同一の Task を射影するだけ |
| VIEW-02 | ビュー間の変換ロジックを lib/view/ に集約すること。 コンポーネントに status === 'doing' のような分岐を散らさない |
| VIEW-03 | ビューを切り替えても、絞り込み条件が維持されること(「担当:自分」で絞ったままボード↔カレンダーを往復できる) |
| VIEW-04 | ビューの切替が 100ms以下であること。 データを取り直さない |
| VIEW-05 | どのビューからでも同じ操作ができること(期限変更・担当変更・完了)。ビューによってできることが変わらない |
| VIEW-06 | 更新は楽観的に反映し、失敗時のみロールバックすること |
VIEW-05 が重要。 「ボードでは担当を変えられるが、カレンダーではできない」といった非対称があると、ユーザーはビューを信用しなくなる。
自然文からのタスク生成(事実①への回答)
依頼から3秒でタスクにするために、1行の自然文を解析する。
入力:「田中さん 来週金曜までに A社の見積書作成 #営業」
↓ lib/parse/quick-add.ts(ルールベース。AIを使わない)
タイトル:A社の見積書作成
担当 :田中(メンバー名と一致)
期限 :来週金曜 → 2026-09-04(相対日付の解決)
案件 :営業(#タグ)
| ID | 規約 |
|---|---|
| PRS-01 | 解析はルールベースで行い、外部AIを呼ばないこと(キー管理が不要/データが外に出ない) |
| PRS-02 | 解析結果を、確定前にチップで可視化すること。 何が切り出されたかが見えること |
| PRS-03 | 切り出しを取り消せること。 チップの × で解除し、タイトルに戻せる |
| PRS-04 | 解析に失敗しても、必ずタスクとして追加できること。 「解析できません」で止めない |
| PRS-05 | 解析ロジックを lib/parse/ に集約し、純粋関数とすること |
| PRS-06 | 解析できる記法を、入力欄の近くに常時ヒント表示すること(覚えさせない) |
PRS-04 が重要。 賢く解析しようとして入力を妨げたら本末転倒。解析は「おまけ」であり、タスクが積まれることが本体。
チャット取り込みのシミュレーション(事実①の可視化)
実案件で最も効くのはチャット連携だが、デモではAPIを叩けない。そこで「取り込みの体験」だけを再現する。
| ID | 要件 |
|---|---|
| IMP-01 | 模擬チャット画面を用意し、そこから「タスクにする」を押せること(SC-030) |
| IMP-02 | メッセージ本文から自然文解析でタスクを生成し、元メッセージへの参照を残すこと |
| IMP-03 | これがシミュレーションであることを明示すること。 実連携ではない旨を画面上に表示 |
| IMP-04 | 実案件での連携方式(Webhook・Bot・メール転送)を、/how-it-works で説明すること |
「チャットの1メッセージが、そのままタスクになる」体験ができれば、事実①の解決策が伝わる。
差し替え可能性の担保
データアクセスは必ず lib/repo/ のリポジトリ層を経由すること。 コンポーネントからストアを直接触らない。
[ コンポーネント ]
↓ 呼ぶのはこの層だけ
[ lib/repo/*.ts ] ← 全メソッドを async にしておく
↓
[ lib/view/ ] ← 3ビューへの射影(純粋関数)
[ lib/parse/ ] ← 自然文解析(純粋関数)
[ lib/recur/ ] ← 繰り返し生成(純粋関数)
[ lib/graph/ ] ← 依存関係・進捗(純粋関数)
[ lib/store/*.ts ] ← Zustand + persist(localStorage)
↓
[ lib/seed/*.ts ] ← 初期データ
リポジトリの各メソッドは、中身が同期処理でも async で定義し、await で呼ぶ。実案件へ転用する際、リポジトリの実装だけをAPI呼び出しに差し替えれば、UI層は一行も変更せずに済む。
記事への組み込み
| 段 | 配置 | 中身 |
|---|---|---|
| ① 記事内インライン | 記事の冒頭〜中盤、<iframe> |
1行入力欄 + 小さなボード。 自然文を打つと解析され、カードがボードに積まれる |
| ② 全画面デモ | 「全画面で試す」カード → 別タブ | アプリ全体(3ビュー・繰り返し・依存・チャット取り込み・分析) |
記事内で見せるのは「入れるのがどれだけ速いか」だけに絞る。 これが事実①そのものであり、スクロール中に体験できる唯一のことになる。
| ID | 要件 |
|---|---|
| EMB-01 | 記事内 iframe は loading="lazy" とし、ビューポート接近まで読み込まないこと |
| EMB-02 | 埋め込みモードのJS初期バンドルは 65KB以下(gzip後)とすること |
| EMB-03 | 埋め込み iframe が記事の LCP 要素にならないこと |
| EMB-04 | 埋め込みモードで実際にタスクを追加でき、解析結果がチップで表示されること(これが主役) |
| EMB-05 | 追加までの秒数を表示すること(「2.4秒で追加」) |
| EMB-06 | サンプルの入力例を3つ提示し、タップで入力欄に入ること(何を打てばいいか分からない状態を作らない) |
| EMB-07 | 埋め込みモードで小さなボード(3列)にカードが積まれ、ドラッグできること |
| EMB-08 | 埋め込みモードではアプリ内遷移をせず、「全画面で試す」を target="_blank" で提示すること |
| EMB-09 | GA4 で計測すること:demo_open / task_added(タスクを追加した)/add_seconds(追加までの秒数)/parse_hit(解析で何が切り出されたか:due/assignee/tag)/card_dragged / view_switched / recurring_generated / role_switch / doc_download / form_click / line_click |
| EMB-10 | add_seconds と parse_hit は最重要指標。 読者が何秒で積めたか、解析が効いたかは、記事の主張を裏付ける実データになる |
| EMB-11 | タスクのタイトル・本文を GA4 に送らないこと。 送るのは秒数・解析ヒット種別などのメタ情報のみ |
| EMB-12 | ?embed=1 のURLは noindex とすること。デモ本体は固有の title / description を持つこと |
永続化の範囲
| 対象 | 挙動 |
|---|---|
| シードデータ(メンバー・プロジェクト・タスク・コメント) | 初期投入。マスタは編集可能 |
| タスクの追加・更新・完了・並び替え・繰り返し生成 | localStorage に保存。リロードしても残る |
| ビューの状態(絞り込み・並び順・表示列・グループ化) | localStorage に保存 |
| 添付ファイル | IndexedDB(Blob)。サーバーには送らない |
| リセット | 「デモをリセット」で localStorage / IndexedDB をクリア |
擬似ディレイ は 150〜400ms。ただしタスクの追加・ドラッグ&ドロップ・ビュー切替・絞り込みは擬似ディレイを入れず、100ms以下とする(ドラッグがもたつくのは、このアプリで最も致命的)。
3. スコープ定義
積む(入力)
| ID | 機能 | 概要 | 優先度 |
|---|---|---|---|
| F-A01 | クイック追加 | 1行入力。自然文から期限・担当・タグを解析。解析結果をチップ表示 | Must |
| F-A02 | ⌘K からの追加 |
どの画面からでも呼び出して追加。画面遷移しない | Must |
| F-A03 | 入力時間の表示 | 追加までの秒数を控えめに表示 | Must |
| F-A04 | 詳細フォームでの追加 | 説明・添付・サブタスク・依存を含む本格的な作成 | Must |
| F-A05 | テンプレートからの追加 | よくある一式(例:新規案件の初動5タスク)をまとめて生成 | Must |
| F-A06 | チャット取り込み(模擬) | 模擬チャット画面から「タスクにする」(2章 IMP) | Must |
| F-A07 | 複数行の一括追加 | 改行区切りで複数タスクを一度に | Should |
| F-A08 | CSVインポート | 既存のExcel/スプレッドシートから取り込み | Should |
| F-A09 | メールからの追加(模擬) | 模擬メール画面から「タスクにする」 | Could |
捌く(3ビュー)
| ID | 機能 | 概要 | 優先度 |
|---|---|---|---|
| F-V01 | ボードビュー | ステータス別の列。D&Dで移動。列ごとの件数・WIP上限 | Must |
| F-V02 | リストビュー | 表形式。絞り込み・並び替え・グループ化・列カスタマイズ | Must |
| F-V03 | カレンダービュー | 月/週。期限で配置。D&Dで期限変更 | Must |
| F-V04 | ビュー間の同期 | 同一データの射影。切り替えても絞り込みが維持される(2章 VIEW) | Must |
| F-V05 | 今日のリスト | 自分の今日やること。期限順。担当者の初期画面 | Must |
| F-V06 | 絞り込み | 担当・プロジェクト・タグ・期限・ステータス・優先度 | Must |
| F-V07 | 保存ビュー | 絞り込み + 表示設定を保存。個人/共有 | Must |
| F-V08 | グループ化 | 担当別・プロジェクト別・期限別・優先度別 | Should |
| F-V09 | 検索 | タイトル・説明・コメントの全文検索 | Must |
| F-V10 | タイムライン(ガント) | 期間を持つタスクを横棒で。依存を線で結ぶ | Should |
進める(タスクの中身)
| ID | 機能 | 概要 | 優先度 |
|---|---|---|---|
| F-T01 | タスク詳細 | タイトル・説明・担当・期限・優先度・ステータス・タグ・添付 | Must |
| F-T02 | サブタスク | チェックリスト。親の進捗に反映 | Must |
| F-T03 | 依存関係 | 「AがBの前」。前工程が未完了なら着手を警告 | Must |
| F-T04 | コメント | やり取り。メンション | Must |
| F-T05 | 添付ファイル | 画像・PDF。端末内で保持 | Should |
| F-T06 | 繰り返し | 毎日/毎週/毎月/営業日。完了時に次回分を自動生成 | Must |
| F-T07 | 見積時間・実績時間 | 任意入力。入力を強制しない | Should |
| F-T08 | 完了 | 1タップ。取り消せる | Must |
| F-T09 | 一括操作 | 複数選択して担当変更・期限変更・完了・削除 | Must |
| F-T10 | 履歴 | 誰がいつ何を変えたか | Should |
管理する
| ID | 機能 | 概要 | 優先度 |
|---|---|---|---|
| F-M01 | チームボード | 誰が何を持っているか。担当別の列 | Must |
| F-M02 | 負荷の可視化 | 担当者別のタスク数・期限超過数。偏りが見える | Must |
| F-M03 | 期限超過・停滞の検出 | 期限切れ、一定日数動いていないタスク | Must |
| F-M04 | プロジェクト管理 | プロジェクトの作成、メンバー、進捗率 | Must |
| F-M05 | 繰り返しタスクの管理 | 定義の一覧、次回生成日、停止 | Must |
| F-M06 | テンプレート管理 | タスクセットの定義 | Must |
| F-M07 | メンバー・権限管理 | メンバー、ロール、所属プロジェクト | Must |
| F-M08 | 完了率・リードタイム分析 | 期間別の完了数、着手から完了までの日数 | Should |
| F-M09 | ステータス・タグのマスタ | 列の定義、色、WIP上限 | Must |
| F-M10 | 通知設定 | 期限前、担当割当、メンション、停滞 | Should |
| F-M11 | エクスポート | CSV出力 | Should |
共通・基盤
| ID | 機能 | 概要 | 優先度 |
|---|---|---|---|
| F-C01 | ロール切替 | 担当者/リーダー/管理者 をワンクリック切替 | Must |
| F-C02 | 埋め込みモード | ?embed=1 でクイック追加 + 小ボード(2章) |
Must |
| F-C03 | コマンドパレット | ⌘K で検索・追加・移動・ビュー切替 |
Must |
| F-C04 | 通知センター | 割当・期限・メンション・停滞 | Must |
| F-C05 | 仕組みの解説 | 3ビュー同期・自然文解析・チャット連携の説明(/how-it-works) |
Must |
| F-C06 | 記事への導線 | 元記事へのリンク、資料ダウンロード、問い合わせ | Must |
| F-C07 | デモリセット | localStorage / IndexedDB をクリア | Must |
| F-C08 | PWA | ホーム画面追加、オフライン起動 | Should |
| F-C09 | ガイドツアー | 初回訪問時に「何を試せるか」を3ステップで案内 | Should |
対象外(Won't have)
| 項目 | 理由 |
|---|---|
| データベース・バックエンドAPI | 2章の方針に基づく |
| 本物の認証・SSO | ロール切替で代替 |
| 実際のチャット・メール連携 | 模擬画面で再現(IMP-03)。実連携は11章で費用論点として扱う |
| リアルタイム共同編集(同時カーソル) | 対象外。 単体で数週間の領域 |
| AIによるタスク分解・優先度提案 | 対象外。 自然文解析はルールベース(PRS-01)。AIは11章で論点として扱う |
| 工数・原価管理、請求連携 | 見積/実績時間の記録までが対象 |
| 勤怠・出退勤との連携 | TOKIWA(勤怠)の領域 |
| 日報としての利用 | HIBI(日報)の領域 |
| Wiki・ドキュメント管理 | 対象外 |
| 顧客管理・案件管理としての利用 | RELATE(顧客管理)の領域。プロジェクト管理までが対象 |
| ポートフォリオ管理・リソース最適化 | 負荷の可視化までに留める |
| 多言語UI | 日本語のみ |
| ネイティブアプリ | PWAで対応 |
スコープリスク: タスク管理は「あのツールのあの機能が欲しい」が最も出やすい領域。既製ツールの機能を1つずつ足していくと、劣化コピーになって必ず負ける。 自作の価値は「既製ツールにない、自社固有の何か」にしかない(11章)。それが何かを要件定義で確定させることが最重要。 資料PDFの「自作判断シート」はこのために作る。
4. ロールとデモ切替
ロール定義
| ロールID | 名称 | 見えるデータ | 特徴的な権限 |
|---|---|---|---|
member |
担当者 | 自分が担当・関係するタスク + 所属プロジェクトのタスク | タスクの作成・更新・完了、コメント |
leader |
リーダー | 自チーム全員のタスク | 上記 + 担当の割当・変更、優先度の設定、チームボードの閲覧、負荷の確認 |
admin |
管理者 | 全社 | 上記 + プロジェクト管理・ステータス定義・テンプレート・メンバー管理・分析 |
データスコープの要点
| ID | 要件 |
|---|---|
| SCP-01 | データスコープを lib/repo/_scope.ts に集約すること |
| SCP-02 | コンポーネント側で role === 'member' のような分岐を書かないこと |
| SCP-03 | 担当者は、所属していないプロジェクトのタスクを一切取得できないこと |
| SCP-04 | 非公開プロジェクトの概念を持ち、メンバー以外に一切返さないこと(検索結果にも出さない) |
| SCP-05 | 見積時間・実績時間は担当者本人とリーダー以上のみが閲覧できること |
SCP-04 が重要。 「検索したら見えないはずのタスクのタイトルが出た」は、タスク管理では致命的な事故になる(人事案件・M&A案件などが混ざるため)。検索インデックスの段階で除外する。
ロールを跨ぐ体験(必ず動くようにする)
- 担当者がタスクをクイック追加(「田中さん 来週金曜 A社見積 #営業」)→ リーダーに切替 → チームボードの田中の列に現れている
- リーダーがボードでカードを別の担当に動かす → 担当者に切替 → 自分の今日のリストから消えている/現れている
- リストビューで期限を変更 → カレンダービューに切り替えると、その日付に移動している(VIEW-01)
- カレンダーでカードを別の日にドラッグ → リストの期限が変わっている
- 繰り返しタスクを完了 → 次回分が自動で生成され、カレンダーの次の日付に現れている
- 依存関係のあるタスクを、前工程が未完了のまま着手しようとする → 警告が出る
- 管理者がステータス列を1つ追加 → 担当者に切替 → ボードの列が増えている
- リーダーが負荷を確認 → 1人にタスクが偏っていることが見え、その場で担当を振り替えられる
3・4・5 が、このデモの核心。 事実②③が体験として成立する瞬間。
デモ切替バー(F-C01)
画面上部に常時表示する固定バー。埋め込みモードでは非表示。
- 現在のロールと氏名・チームの表示、3ロールの切替
- 担当者ロール時:3名から選択(タスクが多い人/期限超過がある人/新人)
- デモ内の現在日時(シードが相対日付のため基準を示す)
- 「デモをリセット」ボタン(確認ダイアログ付き)
- 元記事へ戻るリンク
- 「これはデモです。タスクの内容は送信されません」の明示
デザイン上の扱い: プロダクト本体が生成りの紙色で明るいので、切替バーは濃い墨色の帯にして区別する。
5. 画面一覧とユーザーフロー
全30画面。画面IDはディレクトリ構成と 1:1 で対応させる。
積む・捌く(担当者)
| 画面ID | 画面名 | パス | 主要要素 |
|---|---|---|---|
| SC-001 | 今日 | / |
自分の今日やること(期限順)、期限超過、クイック追加欄、完了数 |
| SC-010 | ボードビュー | /board |
ステータス別の列、D&D、件数・WIP上限、絞り込み |
| SC-011 | リストビュー | /list |
表形式、絞り込み・並び替え・グループ化・列カスタマイズ、一括操作 |
| SC-012 | カレンダービュー | /calendar |
月/週、期限で配置、D&Dで期限変更、未設定タスクのサイドバー |
| SC-013 | タイムライン(ガント) | /timeline |
期間を持つタスクの横棒、依存を線で結ぶ |
| SC-014 | ビュー共通のツールバー | 各ビュー上部 | ビュー切替、絞り込みチップ、保存ビュー、グループ化、表示設定 |
| SC-020 | タスク詳細 | /tasks/[id] |
全項目、サブタスク、依存、コメント、添付、履歴 |
| SC-021 | クイック追加(インライン) | 各ビュー内 | 1行入力 + 解析チップ + 追加秒数 |
| SC-022 | コマンドパレット | ⌘K |
検索・追加・移動・ビュー切替 |
| SC-023 | 詳細フォームでの追加 | モーダル | 説明・添付・サブタスク・依存・繰り返し |
| SC-024 | テンプレートからの追加 | モーダル | タスクセットの選択、プレビュー、日付の起点指定 |
| SC-030 | チャット取り込み(模擬) | /inbox/chat |
模擬チャット。「タスクにする」で自然文解析 |
| SC-031 | メール取り込み(模擬) | /inbox/mail |
模擬メール。「タスクにする」 |
| SC-032 | CSVインポート | /import |
列マッピング、プレビュー、取込結果 |
| SC-040 | 検索 | /search |
全文検索、絞り込み、ハイライト |
| SC-041 | 通知センター | /notifications |
割当・期限・メンション・停滞 |
| SC-050 | 仕組みの解説 | /how-it-works |
3ビュー同期・自然文解析・チャット連携の実装方法 |
| SC-900 | 埋め込みモード | /?embed=1 |
クイック追加 + 小ボード(2章) |
管理する(リーダー・管理者)
| 画面ID | 画面名 | パス | 主要要素 |
|---|---|---|---|
| SC-100 | チームボード | /team/board |
担当者別の列、未割当の列、D&Dで振り替え |
| SC-101 | 負荷の可視化 | /team/load |
担当者別のタスク数・期限超過・見積時間合計。偏りの強調 |
| SC-102 | 要対応の抽出 | /team/attention |
期限超過、停滞(N日動いていない)、未割当、依存でブロック中 |
| SC-103 | チームの今日 | /team/today |
全員の今日のタスク |
| SC-110 | プロジェクト一覧 | /projects |
進捗率、期限、メンバー、タスク数 |
| SC-111 | プロジェクト詳細 | /projects/[id] |
タスク、進捗、メンバー、タイムライン |
| SC-120 | 繰り返しタスクの管理 | /admin/recurring |
定義一覧、次回生成日、停止・再開、生成履歴 |
| SC-121 | テンプレート管理 | /admin/templates |
タスクセットの定義、相対日付の設定、プレビュー |
| SC-130 | ステータス・列の定義 | /admin/statuses |
列の追加・並び替え・色・WIP上限、完了扱いの指定 |
| SC-131 | タグ・優先度のマスタ | /admin/tags |
タグ、色、使用件数、未使用の検出 |
| SC-140 | メンバー・権限管理 | /admin/members |
メンバー、ロール、所属プロジェクト |
| SC-150 | 完了率・リードタイム分析 | /admin/analytics |
期間別の完了数、着手→完了の日数、担当者別・プロジェクト別 |
| SC-151 | 通知設定 | /admin/notifications |
期限前・割当・メンション・停滞の文面と条件 |
| SC-160 | エクスポート | /admin/export |
CSV出力、期間・条件の指定 |
主要フロー
フローA:記事の読者が「積む速さ」を体験する(最重要)
SEO記事を読んでいる
↓
記事内の埋め込み(SC-900)
┌──────────────────────────────────────────────────────────┐
│ + タスクを追加 │
│ ┌────────────────────────────────────────────────────┐ │
│ │ 田中さん 来週金曜までに A社の見積書作成 #営業 │ │
│ └────────────────────────────────────────────────────┘ │
│ 💡 「明日」「来週金曜」「@名前」「#タグ」が使えます │
│ │
│ 例を試す: │
│ [ 明日 請求書を送る ] [ @佐藤 今週中に資料レビュー ] │
│ [ 毎週月曜 週次ミーティングの準備 ] │
└──────────────────────────────────────────────────────────┘
↓
入力すると、打っている最中に解析チップが現れる
┌──────────────────────────────────────────────────────┐
│ A社の見積書作成 │
│ [👤 田中 ×] [📅 9/4(金) ×] [🏷 営業 ×] │
└──────────────────────────────────────────────────────┘
★ 何が切り出されたかが見える(PRS-02)
★ チップの × で解除してタイトルに戻せる(PRS-03)
↓
Enter で追加
↓ ★ 小さなボードの「未着手」列にカードが積まれる
★ 「2.4秒で追加」と控えめに表示される
↓
カードを「進行中」にドラッグ
↓ ★ 引っかからずに動く。ここの手触りが命
↓
「全画面で試す」→ 別タブ
↓
SC-010 ボードビュー ── 実データが入ったボード
↓
ビュー切替でカレンダーへ ── ★ 絞り込みが維持されたまま切り替わる(VIEW-03)
↓ ★ さっき追加したタスクが 9/4 の枠にいる
カードを 9/8 にドラッグ
↓
リストビューに切替 ── ★ 期限が 9/8 に変わっている(VIEW-01)
↓
ロール切替「リーダー」→ SC-100 チームボード
↓ ★ 田中の列にそのタスクがある
SC-101 負荷の可視化 ── ★ 担当の偏りが見える
↓
資料ダウンロード or 元記事に戻る
フローB:チャットの依頼が3秒でタスクになる(事実①の解決策を見せる)
SC-030 チャット取り込み(模擬)
┌──────────────────────────────────────────────────────────┐
│ ⚠ これは模擬チャットです。実際のSlack等には接続していません │
│ │
│ #営業チーム │
│ ───────────────────────────────────────────────────── │
│ 佐藤 10:32 │
│ 田中さん、A社の件、来週金曜までに見積出せますか? │
│ [ タスクにする ] │
│ ───────────────────────────────────────────────────── │
│ 田中 10:35 │
│ 了解です。あと請求書の件も今日中にやっておきます │
│ [ タスクにする ] │
└──────────────────────────────────────────────────────────┘
↓
1つ目の「タスクにする」を押す
↓ ★ 自然文解析が走り、確認モーダルが開く
┌──────────────────────────────────────────────────┐
│ タイトル:A社の件、見積を出す │
│ [👤 田中 ×] [📅 9/4(金) ×] │
│ │
│ 元メッセージ:佐藤 10:32(リンクを保持します) │
│ │
│ [ キャンセル ] [ タスクにする ] │
└──────────────────────────────────────────────────┘
↓
確定
↓ ★ タスクが作られ、詳細に元メッセージへの参照が残る(IMP-02)
SC-020 タスク詳細 ── ★ 「チャットから作成(#営業チーム 佐藤 10:32)」
↓
SC-050 仕組みの解説
★ 「実際の連携では、Slack の Bot やメール転送で同じことができます」
★ 実装方式(Webhook / Bot / メール転送)とそれぞれの費用感(IMP-04)
↓
★ ここで伝わること:
「タスク管理が続かないのは、入力が二重作業だから。
依頼が発生した場所から直接タスクになれば、その問題は消える」
フローC:繰り返しと依存で「段取り」を回す
【管理者】SC-121 テンプレート管理
「新規案件の初動」テンプレートを作る
┌────────────────────────────────────────────────┐
│ 1. キックオフ日程の調整 起点日 +0日 @営業 │
│ 2. 要件ヒアリング 起点日 +3日 @営業 │
│ 3. 見積作成 起点日 +7日 @PM │ ← 2 に依存
│ 4. 提案書作成 起点日 +10日 @PM │ ← 3 に依存
│ 5. 提案 起点日 +14日 @営業 │ ← 4 に依存
└────────────────────────────────────────────────┘
↓
【リーダー】SC-024 テンプレートからの追加
起点日を「来週月曜」に指定 → プレビューで日付が計算される
↓
5タスクが一括生成され、依存関係も張られる
↓
SC-013 タイムライン ── ★ 依存が線で結ばれ、順序が見える
↓
【担当者】SC-001 今日
「見積作成」に着手しようとする
↓ ★ 警告:「『要件ヒアリング』が未完了です。先に進めますか?」(F-T03)
★ ブロックはしない。現実には並行して進むこともあるから
↓
─────────────────────────────────────────────────────
【繰り返しの体験】
SC-020 タスク詳細 ──「毎月の請求処理」(繰り返し:毎月25日)
↓
完了にする
↓ ★ 次回分(翌月25日)が自動生成される(F-T06)
★ トースト:「次回分を 9/25 に作成しました」
↓
SC-012 カレンダー ── ★ 翌月25日にカードが現れている
↓
SC-120 繰り返しタスクの管理
★ 定義の一覧、次回生成日、生成履歴が確認できる
★ 「もう不要」なら停止できる(過去の記録は消えない)
↓
★ ここで伝わること:
「毎月同じタスクを手で作る作業は、システムがやるべきこと」
6. 機能要件
各要件はテスト仕様書の項目と1:1で対応する。「〜できること」の粒度で、判定可能な形で記述している。
6.1 入力体験(このデモの心臓部)
| ID | 要件 | 優先 |
|---|---|---|
| FR-101 | 1行のクイック追加ができ、Enter で確定すること。確認ダイアログを出さないこと | P1 |
| FR-102 | 自然文から期限・担当・タグ・優先度を解析すること(2章 PRS) | P1 |
| FR-103 | 相対日付を解決すること:今日/明日/明後日/今週◯曜/来週◯曜/N日後/月末/来月◯日 | P1 |
| FR-104 | 担当を @名前 またはメンバー名の直接記述で解析すること(「田中さん」でも解析すること) |
P1 |
| FR-105 | タグを #タグ名 で解析すること。 未登録のタグは新規作成の確認を出すこと |
P1 |
| FR-106 | 優先度を ! !! !!! で解析できること |
P2 |
| FR-107 | 繰り返しを「毎日/毎週◯曜/毎月N日/毎営業日」で解析できること | P2 |
| FR-108 | 解析結果を、打っている最中にチップで表示すること(PRS-02) | P1 |
| FR-109 | チップの × で解析を取り消し、その語をタイトルに戻せること(PRS-03) |
P1 |
| FR-110 | 解析できなくても必ず追加できること。「解析できません」で止めないこと(PRS-04) | P1 |
| FR-111 | 解析できる記法のヒントを、入力欄の近くに常時表示すること(PRS-06) | P1 |
| FR-112 | 追加までの秒数を控えめに表示すること(大きくしない、急かさない) | P1 |
| FR-113 | ⌘K / Ctrl+K からどの画面でもタスクを追加できること。画面遷移しないこと |
P1 |
| FR-114 | 改行区切りで複数タスクを一括追加できること | P2 |
| FR-115 | 追加が 100ms以下で完了し、その場にカードが現れること。擬似ディレイを入れないこと | P1 |
| FR-116 | 詳細フォーム(説明・添付・サブタスク・依存・繰り返し)からも追加できること | P1 |
| FR-117 | テンプレートから複数タスクを一括生成できること。 起点日を指定すると相対日付が計算されること | P1 |
| FR-118 | テンプレート内のタスクに依存関係を定義でき、生成時に張られること | P1 |
| FR-119 | CSVインポート(列マッピング・プレビュー・エラー行表示)ができること | P2 |
| FR-120 | 数式インジェクション(=, +, -, @ 始まり)を無害化すること |
P1 |
6.2 3ビューと同期
| ID | 要件 | 優先 |
|---|---|---|
| FR-201 | ボード・リスト・カレンダーが同一の Task を射影すること。ビューごとに別データを持たないこと(VIEW-01) |
P1 |
| FR-202 | ビュー間の変換を lib/view/ に集約すること(VIEW-02) |
P1 |
| FR-203 | ビューを切り替えても絞り込み条件が維持されること(VIEW-03) | P1 |
| FR-204 | ビュー切替が 100ms以下であること。データを取り直さないこと(VIEW-04) | P1 |
| FR-205 | どのビューからでも、期限変更・担当変更・完了ができること(VIEW-05) | P1 |
| FR-206 | ボードで列間のD&Dによりステータスを変更できること。 列内の並び替えもできること | P1 |
| FR-207 | カレンダーで日付間のD&Dにより期限を変更できること | P1 |
| FR-208 | リストで期限を変更すると、カレンダーとボードに即座に反映されること | P1 |
| FR-209 | D&Dをキーボードでも行えること(カード選択 → 移動先選択) | P1 |
| FR-210 | ドラッグ中のフレームレートが 60fps を維持すること | P1 |
| FR-211 | 更新は楽観的に反映し、失敗時のみロールバックすること(VIEW-06) | P1 |
| FR-212 | ボードの列に件数と WIP上限を表示し、超過時に警告色にすること | P1 |
| FR-213 | カレンダーに期限未設定のタスクをサイドバーで表示し、日付にドラッグして期限を設定できること | P1 |
| FR-214 | カレンダーは月/週を切り替えられること | P1 |
| FR-215 | タイムライン(ガント)で、期間を持つタスクを横棒で表示し、依存を線で結ぶこと | P2 |
| FR-216 | タイムラインで横棒をドラッグして期間を変更できること | P2 |
6.3 一覧・絞り込み・保存ビュー
| ID | 要件 | 優先 |
|---|---|---|
| FR-301 | 絞り込み条件:担当・プロジェクト・タグ・期限(範囲)・ステータス・優先度・完了/未完了 | P1 |
| FR-302 | 絞り込み条件をURLクエリに反映し、リロード・URL共有で再現できること | P1 |
| FR-303 | 適用中の絞り込み条件をチップで表示し、個別に解除できること | P1 |
| FR-304 | 絞り込み + ビュー種別 + グループ化 + 表示列 を「ビュー」として保存できること | P1 |
| FR-305 | 保存ビューをサイドバーに一覧表示し、件数バッジ付きでワンクリック切替できること | P1 |
| FR-306 | 個人ビューと共有ビュー(リーダー以上が作成)を区別すること | P1 |
| FR-307 | グループ化(担当別・プロジェクト別・期限別・優先度別)ができること | P2 |
| FR-308 | リストの表示列をユーザーが選択・並び替えできること | P2 |
| FR-309 | 行のチェックボックス選択で**一括操作(担当変更・期限変更・完了・削除・タグ付与)**ができること | P1 |
| FR-310 | 一括操作の実行前に、対象件数と操作内容の確認ダイアログを表示すること | P1 |
| FR-311 | 全文検索(タイトル・説明・コメント)ができ、ヒット箇所をハイライトすること | P1 |
| FR-312 | 検索が 100ms以下で完了すること(タスク1,200件 + コメント900件規模) | P1 |
| FR-313 | 非公開プロジェクトのタスクを、検索インデックスの段階で除外すること(SCP-04) | P1 |
| FR-314 | 行数が多くても描画がもたつかないこと(仮想スクロール) | P1 |
6.4 タスクの中身
| ID | 要件 | 優先 |
|---|---|---|
| FR-401 | タスクに、タイトル・説明・担当・期限・開始日・優先度・ステータス・プロジェクト・タグ・添付 を持たせること | P1 |
| FR-402 | サブタスク(チェックリスト)を持て、親タスクの進捗率に反映されること | P1 |
| FR-403 | サブタスクを並び替えられること。キーボードでも操作できること | P2 |
| FR-404 | 依存関係(このタスクの前に完了すべきタスク)を設定できること | P1 |
| FR-405 | 前提タスクが未完了のまま着手(ステータス変更)しようとした場合、警告を表示すること。ただしブロックしないこと | P1 |
| FR-406 | 依存の循環を検出し、設定時に防ぐこと | P1 |
| FR-407 | 依存によりブロック中のタスクを、一覧で抽出できること | P2 |
| FR-408 | コメントを投稿でき、メンションで通知されること(プレビュー表示で再現) | P1 |
| FR-409 | 添付ファイル(画像・PDF、1ファイル10MBまで)を端末内(IndexedDB)に保持できること | P2 |
| FR-410 | 完了を1タップで行え、取り消せること(5秒間の「元に戻す」) | P1 |
| FR-411 | 見積時間・実績時間を任意で記録できること。入力を必須にしないこと | P2 |
| FR-412 | タスクの変更履歴(誰がいつ何を変えたか)を保持すること | P2 |
| FR-413 | タスクを複製できること(サブタスク・依存を含むか選べること) | P2 |
| FR-414 | タスクを削除でき、5秒間の「元に戻す」を表示すること | P1 |
6.5 繰り返し(事実②③への回答)
| ID | 要件 | 優先 |
|---|---|---|
| FR-501 | 繰り返しルールを設定できること:毎日/毎週(曜日指定)/毎月(日付指定)/毎月(第N◯曜)/毎営業日/N日ごと | P1 |
| FR-502 | 完了時に次回分を自動生成すること(既定)。または期日到来時に生成する方式も選べること | P1 |
| FR-503 | 次回分の生成をトーストで通知すること(「次回分を 9/25 に作成しました」) | P1 |
| FR-504 | 繰り返しの終了条件(回数・終了日・無期限)を設定できること | P2 |
| FR-505 | 繰り返しタスクの定義を一覧で管理でき、次回生成日を表示すること | P1 |
| FR-506 | 繰り返しを停止・再開できること。停止しても過去に生成されたタスクは消えないこと | P1 |
| FR-507 | 生成履歴を確認できること | P2 |
| FR-508 | 祝日を考慮した「毎営業日」の生成ができること(祝日カレンダーを持つ) | P2 |
| FR-509 | 繰り返しタスクにサブタスクのテンプレートを持たせ、生成時に一緒に作られること | P2 |
| FR-510 | 繰り返し生成のロジックを lib/recur/ に集約し、純粋関数とすること |
P1 |
6.6 チーム管理
| ID | 要件 | 優先 |
|---|---|---|
| FR-601 | チームボードで、担当者別の列にタスクを表示すること。未割当の列を持つこと | P1 |
| FR-602 | 担当者別の列間でD&Dし、担当を振り替えられること | P1 |
| FR-603 | 負荷の可視化:担当者別のタスク数・期限超過数・見積時間合計を表示すること | P1 |
| FR-604 | 偏りを強調表示すること(平均から一定以上離れている担当者) | P1 |
| FR-605 | 要対応の抽出:期限超過/停滞(既定7日動いていない)/未割当/依存でブロック中 | P1 |
| FR-606 | 停滞の判定日数を設定できること | P2 |
| FR-607 | プロジェクトを作成・管理でき、進捗率(完了タスク数 ÷ 全タスク数)を表示すること | P1 |
| FR-608 | 非公開プロジェクトを作成でき、メンバー以外に一切見えないこと(SCP-04) | P1 |
| FR-609 | プロジェクトのタイムラインを表示できること | P2 |
| FR-610 | チームの今日のタスクを一覧できること | P2 |
6.7 設定・マスタ・分析
| ID | 要件 | 優先 |
|---|---|---|
| FR-701 | ステータス(ボードの列)を追加・削除・並び替え・色設定できること | P1 |
| FR-702 | ステータスごとにWIP上限を設定できること | P1 |
| FR-703 | どのステータスを「完了扱い」とするかを指定できること | P1 |
| FR-704 | ステータスの変更が、ボードとすべてのタスクに即座に反映されること | P1 |
| FR-705 | ステータスを削除する際、そのステータスのタスクの移動先を指定させること(タスクを孤立させない) | P1 |
| FR-706 | タグ・優先度のマスタを管理できること。使用件数と未使用タグを表示すること | P2 |
| FR-707 | タスクテンプレート(タスクセット)を定義でき、相対日付・担当・依存を含められること | P1 |
| FR-708 | テンプレートをプレビューでき、起点日を変えると日付が再計算されること | P1 |
| FR-709 | メンバー・ロール・所属プロジェクトを管理できること | P1 |
| FR-710 | 完了率(期間別の完了数/作成数)を表示すること | P2 |
| FR-711 | リードタイム(作成→完了、着手→完了)の平均・中央値を表示すること | P2 |
| FR-712 | 担当者別・プロジェクト別の集計を表示すること | P2 |
| FR-713 | リードタイム・完了数を個人の評価指標として提示しないこと。 個人別ランキングを作らないこと | P1 |
| FR-714 | 通知条件(期限N日前・割当・メンション・停滞)と文面を設定できること | P2 |
| FR-715 | CSVエクスポートができること | P2 |
6.8 取り込み・デモ基盤
| ID | 要件 | 優先 |
|---|---|---|
| FR-801 | 模擬チャット画面から「タスクにする」でタスクを生成できること(IMP-01) | P1 |
| FR-802 | 生成時に自然文解析を適用し、確認モーダルで結果を表示すること | P1 |
| FR-803 | 元メッセージへの参照をタスクに残すこと(IMP-02) | P1 |
| FR-804 | これがシミュレーションであることを画面上に明示すること(IMP-03) | P1 |
| FR-805 | /how-it-works で実連携の方式(Webhook・Bot・メール転送)を説明すること(IMP-04) |
P1 |
| FR-806 | 3ロールをワンクリックで切り替えられること | P1 |
| FR-807 | データスコープを lib/repo/_scope.ts に集約すること(SCP-01, SCP-02) |
P1 |
| FR-808 | 担当者が所属していないプロジェクトのタスクを取得できないこと(SCP-03) | P1 |
| FR-809 | 非公開プロジェクトが、検索を含むあらゆる経路でメンバー以外に返らないこと(SCP-04, FR-313) | P1 |
| FR-810 | 「デモをリセット」で localStorage / IndexedDB をクリアすること | P1 |
| FR-811 | タスク・ビュー状態がリロード後も保持されること | P1 |
| FR-812 | デモ内の現在日時を切替バーに表示すること | P2 |
| FR-813 | ロール権限外の画面では案内画面から切り替えられること。素の404を出さないこと | P1 |
| FR-814 | 権限で不可の操作はボタンを非活性にし、理由をツールチップで示すこと | P1 |
| FR-815 | 元記事へ戻るリンクと資料ダウンロードを常設すること | P1 |
| FR-816 | 初回訪問時に3ステップのガイドツアーを表示し、「今後表示しない」を選べること | P2 |
| FR-817 | 埋め込みモード:クイック追加 + 3列の小ボードを表示し、追加とD&Dができ、アプリ内遷移をしないこと(EMB-04〜08) | P1 |
7. データ設計
型定義(lib/types/)
// ---- 組織・メンバー ----
type Team = { id: string; name: string; leaderId: string }
type Member = {
id: string
name: string // ★ 架空
nameAliases: string[] // ★ 「田中さん」「たなか」でも解析できるように(FR-104)
teamId: string
role: 'member' | 'leader' | 'admin'
avatarUrl: string
color: string // ボードの識別色
isActive: boolean
}
// ---- プロジェクト ----
type Project = {
id: string
name: string
color: string
memberIds: string[]
isPrivate: boolean // ★ メンバー以外に一切返さない(SCP-04)
startDate?: string
dueDate?: string
isArchived: boolean
// 算出値:progressRate, taskCount
}
// ---- ステータス(ボードの列)----
type Status = {
id: string
name: string // 未着手 / 進行中 / レビュー / 完了
color: string
sortOrder: number
isDone: boolean // ★ 完了扱いか(FR-703)
wipLimit?: number // ★ WIP上限(FR-702)
}
type Tag = { id: string; name: string; color: string }
type Priority = 'low' | 'normal' | 'high' | 'urgent'
// ---- ★★★ タスク(唯一のデータ。3ビューはこれを射影する)★★★ ----
type Task = {
id: string
title: string
description: string
// --- 分類 ---
projectId?: string
statusId: string // ★ ボードの列を決める
priority: Priority
tagIds: string[]
// --- 担当と日付 ---
assigneeId?: string // ★ チームボードの列を決める
startDate?: string
dueDate?: string // ★ カレンダーの位置を決める
// --- 構造 ---
parentTaskId?: string // 親タスク(サブタスクの場合)
subtaskIds: string[]
dependsOnTaskIds: string[] // ★ 前提タスク(FR-404)
// --- 繰り返し ---
recurrenceId?: string // 繰り返し定義への参照
generatedFromRecurrence: boolean
// --- 時間 ---
estimateMinutes?: number // ★ 権限で出し入れ(SCP-05)
actualMinutes?: number
// --- 完了 ---
completedAt?: string
completedBy?: string
// --- 出所(★ 取り込み元。IMP-02)---
source: TaskSource
// --- 添付・コメント ---
attachmentIds: string[]
// --- 並び順 ---
boardOrder: number // 列内の並び順
// --- 監査 ---
createdBy: string
createdAt: string
updatedAt: string
history: TaskChange[]
// --- ★ 入力の計測(EMB-05, FR-112)---
addMetrics?: { activeSeconds: number; method: 'quick' | 'form' | 'template' | 'chat' | 'csv'; parsedFields: string[] }
}
type TaskSource =
| { kind: 'manual' }
| { kind: 'template'; templateId: string }
| { kind: 'recurrence'; recurrenceId: string }
| { kind: 'chat'; channel: string; author: string; postedAt: string; messageId: string } // ★ IMP-02
| { kind: 'mail'; from: string; subject: string; receivedAt: string }
| { kind: 'csv'; importedAt: string }
type TaskChange = { at: string; by: string; field: string; before: unknown; after: unknown }
type Comment = { id: string; taskId: string; memberId: string; body: string; mentionedMemberIds: string[]; createdAt: string }
// ---- ★ 自然文解析の結果(PRS-02)----
type ParseResult = {
title: string // 解析語を取り除いた残り
matches: ParseMatch[] // ★ チップとして表示(FR-108)
// ★ 解析できなくても title は必ず返る(PRS-04)
}
type ParseMatch = {
id: string
kind: 'due' | 'start' | 'assignee' | 'tag' | 'priority' | 'recurrence' | 'project'
rawText: string // ★ 元の文字列(チップ解除時にタイトルへ戻す。FR-109)
value: unknown // 解決後の値(Date / memberId / tagId ...)
label: string // チップの表示(「9/4(金)」「田中」)
confidence: 'exact' | 'fuzzy' // 曖昧一致は確認を促す
}
// ---- 繰り返し ----
type RecurrenceRule =
| { type: 'daily'; interval: number }
| { type: 'weekly'; interval: number; weekdays: number[] }
| { type: 'monthly_day'; interval: number; dayOfMonth: number }
| { type: 'monthly_nth'; interval: number; nth: number; weekday: number }
| { type: 'business_days' } // ★ 祝日を除く(FR-508)
type Recurrence = {
id: string
templateTask: Pick<Task, 'title' | 'description' | 'projectId' | 'statusId' | 'priority'
| 'tagIds' | 'assigneeId' | 'estimateMinutes'>
subtaskTitles: string[] // FR-509
rule: RecurrenceRule
generateMode: 'on_complete' | 'on_due' // ★ FR-502
endCondition: { type: 'never' } | { type: 'count'; count: number } | { type: 'until'; date: string }
generatedCount: number
nextGenerateDate?: string // ★ FR-505
isActive: boolean // ★ 停止しても過去分は残る(FR-506)
createdAt: string
}
type RecurrenceLog = { id: string; recurrenceId: string; generatedTaskId: string; generatedAt: string }
// ---- テンプレート(タスクセット)----
type TaskTemplate = {
id: string
name: string
description: string
items: TaskTemplateItem[]
createdBy: string
}
type TaskTemplateItem = {
id: string
title: string
description: string
offsetDays: number // ★ 起点日からの相対日数(FR-708)
durationDays?: number
assigneeId?: string
assigneeRole?: string // 「営業」「PM」等、役割で指定して生成時に解決
statusId: string
priority: Priority
tagIds: string[]
dependsOnItemIds: string[] // ★ テンプレート内での依存(FR-118)
subtaskTitles: string[]
sortOrder: number
}
// ---- ビュー ----
type ViewKind = 'board' | 'list' | 'calendar' | 'timeline'
type SavedView = {
id: string
name: string
ownerId: string
isShared: boolean // leader 以上のみ true
kind: ViewKind
filters: Filter[]
groupBy?: 'assignee' | 'project' | 'due' | 'priority' | 'status'
sort: { field: string; dir: 'asc' | 'desc' }[]
visibleColumns: string[] // list のみ
calendarMode?: 'month' | 'week'
}
type Filter = {
field: 'assigneeId' | 'projectId' | 'tagIds' | 'statusId' | 'priority' | 'dueDate' | 'isDone'
operator: 'eq' | 'in' | 'between' | 'isEmpty' | 'isNotEmpty' | 'before' | 'after'
value: unknown
}
// ---- ★ ビューへの射影(保存しない。lib/view/ で算出)----
type BoardProjection = {
columns: { statusId: string; name: string; color: string; wipLimit?: number
; taskIds: string[]; count: number; isOverWip: boolean }[]
}
type CalendarProjection = {
mode: 'month' | 'week'
days: { date: string; taskIds: string[]; isToday: boolean; isHoliday: boolean }[]
unscheduledTaskIds: string[] // ★ 期限未設定(FR-213)
}
type TimelineProjection = {
rows: { taskId: string; startDate: string; endDate: string; laneIndex: number }[]
dependencies: { fromTaskId: string; toTaskId: string }[]
}
// ---- 算出値(ストアに保存しない)----
type LoadMetrics = { // FR-603
memberId: string
name: string
openCount: number
overdueCount: number
estimateMinutesTotal?: number // ★ 権限依存(SCP-05)
deviationFromAvg: number // ★ 偏りの強調に使う(FR-604)
}
type AttentionItems = { // FR-605
overdue: string[]
stalled: { taskId: string; daysSinceUpdate: number }[]
unassigned: string[]
blocked: { taskId: string; blockingTaskIds: string[] }[]
}
type LeadTimeStats = { scope: string; createdToDoneDays: { avg: number; median: number }
; startedToDoneDays: { avg: number; median: number }; sampleCount: number }
// ★ 個人別ランキングは作らない(FR-713)
// ---- 模擬チャット(IMP-01)----
type MockChannel = { id: string; name: string }
type MockMessage = {
id: string
channelId: string
authorName: string
body: string
postedAt: string
convertedTaskId?: string // タスク化済み
}
// ---- 監査・通知 ----
type AuditLog = {
id: string
actorId: string
action: 'update_status_master' | 'delete_status' | 'update_template' | 'stop_recurrence'
| 'delete_task' | 'bulk_update' | 'export_csv'
targetType: string
targetId: string
changes: { field: string; before: unknown; after: unknown }[]
createdAt: string
}
type Notification = {
id: string
targetMemberId: string
kind: 'assigned' | 'due_soon' | 'overdue' | 'mentioned' | 'stalled' | 'recurrence_generated'
title: string
body: string
link: string
previewMessage?: { channel: 'email' | 'chat'; subject?: string; body: string } // ★ 送信しない
readAt?: string
createdAt: string
}
自然文解析の実装(7章の中核)
// lib/parse/quick-add.ts — ルールベース。AIを呼ばない(PRS-01)
export function parseQuickAdd(input: string, ctx: ParseContext): ParseResult {
const matches: ParseMatch[] = []
let rest = input
// ① 相対日付(FR-103)。長いパターンから順に試す
const dueRules: [RegExp, (m: RegExpMatchArray) => Date][] = [
[/来週(月|火|水|木|金|土|日)曜?(?:まで)?(?:に)?/, m => nextWeekWeekday(ctx.now, m[1])],
[/今週(月|火|水|木|金|土|日)曜?(?:まで)?(?:に)?/, m => thisWeekWeekday(ctx.now, m[1])],
[/(\d+)日後/, m => addDays(ctx.now, Number(m[1]))],
[/明後日/, () => addDays(ctx.now, 2)],
[/明日/, () => addDays(ctx.now, 1)],
[/今日(?:中)?/, () => ctx.now],
[/月末(?:まで)?/, () => endOfMonth(ctx.now)],
[/(\d{1,2})\/(\d{1,2})/, m => resolveMonthDay(ctx.now, +m[1], +m[2])],
]
for (const [re, resolve] of dueRules) {
const m = rest.match(re)
if (m) {
const date = resolve(m)
matches.push({ id: newId(), kind: 'due', rawText: m[0], value: date,
label: formatDateLabel(date), confidence: 'exact' })
rest = rest.replace(m[0], ' ') // ★ タイトルから取り除く
break
}
}
// ② 担当(FR-104)。@記法 + 氏名・別名の直接記述
const mentionMatch = rest.match(/@(\S+)/)
const nameHit = mentionMatch
? findMember(ctx.members, mentionMatch[1])
: findMemberByAlias(ctx.members, rest) // 「田中さん」「たなか」
if (nameHit) {
matches.push({ id: newId(), kind: 'assignee', rawText: nameHit.rawText,
value: nameHit.member.id, label: nameHit.member.name,
confidence: mentionMatch ? 'exact' : 'fuzzy' })
rest = rest.replace(nameHit.rawText, ' ')
}
// ③ タグ(FR-105)/④ 優先度(FR-106)/⑤ 繰り返し(FR-107)も同様に抽出
// ★ 何も解析できなくても、title は必ず返す(PRS-04)
return { title: rest.replace(/\s+/g, ' ').trim() || input.trim(), matches }
}
ビュー射影の実装
// lib/view/board.ts — 同一の Task から列を組み立てる(VIEW-01, VIEW-02)
export function toBoard(tasks: Task[], statuses: Status[]): BoardProjection {
return {
columns: statuses
.slice()
.sort((a, b) => a.sortOrder - b.sortOrder)
.map(st => {
const ids = tasks
.filter(t => t.statusId === st.id)
.sort((a, b) => a.boardOrder - b.boardOrder)
.map(t => t.id)
return {
statusId: st.id, name: st.name, color: st.color, wipLimit: st.wipLimit,
taskIds: ids, count: ids.length,
isOverWip: st.wipLimit != null && ids.length > st.wipLimit, // FR-212
}
}),
}
}
シードデータ(lib/seed/)
| ファイル | 内容 | 件数 |
|---|---|---|
teams.ts members.ts |
チーム3、メンバー(別名を持たせる。「田中さん」で解析できるように) | チーム3 / 18名 |
projects.ts |
プロジェクト(非公開を2件含める。SCP-04 の実演用) | 12件 |
statuses.ts |
ステータス5(未着手/進行中/レビュー/保留/完了)。WIP上限を設定したものを1つ | 5件 |
tags.ts |
タグ16(未使用を2つ混ぜる) | 16件 |
tasks.ts |
タスク(過去3ヶ月 + 今後2ヶ月)。期限・担当・ステータスを分散 | 約1,200件 |
comments.ts |
コメント | 約900件 |
recurrences.ts |
繰り返し定義(毎週・毎月・毎営業日を混在。停止中を1つ) | 14件 |
templates.ts |
タスクテンプレート(依存を含む「新規案件の初動」5タスクを含む) | 5件 |
mockChat.ts |
模擬チャット(3チャンネル。タスク化しやすいメッセージを混ぜる) | 約60件 |
シード作成のルール(品質を左右する)
- 実在企業名・実在人名を使わない。 架空のメンバー名・プロジェクト名で構成する
- タスクのタイトルを「タスク1」のようなダミーにしない。 業種の世界観を持つ実在しそうなものにする(「A社向け提案書の作成」「請求書の発行(8月分)」)。タスク管理のデモはタイトルのリアリティがそのまま説得力になる
- 期限の分布に濃淡をつける。 今日・今週に集中し、来月は疎に。均等分布だとカレンダーが不自然
- 期限超過のタスクを作る(担当者によって数を変える)
- 停滞タスク(7日以上更新なし)を数件作る(FR-605 の実演用)
- 未割当のタスクを数件作る(チームボードの「未割当」列が空だと機能が死ぬ)
- 担当の偏りを作る。 1人に明らかにタスクが集中している状態(FR-604 の実演用)
- 依存関係を持つタスクを10組以上作る。 うち数組は前提が未完了のまま進行中(FR-405 の実演用)
- WIP上限を超えている列を1つ作る(FR-212 の実演用)
- サブタスクを持つタスクを2割程度作る。 進捗率が中途半端なものを含める
- 繰り返しから生成されたタスクを含める(
generatedFromRecurrence: true) - 模擬チャットに、タスク化しやすいメッセージを混ぜる(「〜までに〜お願いします」)。逆にタスクにならない雑談も混ぜる(全部がタスクだと嘘に見える)
- 日付は現在日時からの相対で生成する。固定日付を埋め込まない
ストアとリポジトリ
lib/
├── types/ seed/
├── parse/ # ★ 自然文解析。純粋関数のみ
│ ├── quick-add.ts # ★ 7章のコード参照
│ ├── date.ts # 相対日付の解決
│ ├── member.ts # 氏名・別名のあいまい一致
│ └── recurrence.ts # 「毎週月曜」等の解析
├── view/ # ★ 3ビューへの射影。純粋関数のみ
│ ├── board.ts list.ts calendar.ts timeline.ts
│ └── filter.ts # 絞り込み・並び替え・グループ化
├── recur/ # ★ 繰り返し生成。純粋関数のみ
│ ├── next.ts # 次回日付の算出
│ ├── generate.ts # タスクの生成
│ └── holidays.ts # 営業日判定
├── graph/ # ★ 依存関係。純粋関数のみ
│ ├── cycle.ts # 循環検出(FR-406)
│ ├── blocked.ts # ブロック中の抽出(FR-407)
│ └── progress.ts # サブタスクからの進捗率
├── metrics/ # 負荷・リードタイム・完了率(純粋関数)
├── search/ # 全文検索(純粋関数)
├── csv/ storage/ query/
├── store/ { session, data, settings, view, ui }
└── repo/ # ★ コンポーネントが触るのはここだけ
├── _delay.ts
├── _scope.ts # ★ データスコープ(SCP-01〜05)
├── tasks.ts views.ts projects.ts statuses.ts tags.ts
├── recurrences.ts templates.ts comments.ts
├── members.ts metrics.ts search.ts mockChat.ts logs.ts
リポジトリ層の規約(厳守)
- 全メソッドを
asyncで定義する。中身が同期でも例外なく - 戻り値は
{ ok: true; data: T } | { ok: false; error: string }に統一する - コンポーネントから Zustand ストアを直接参照しない。 読み取りも書き込みもリポジトリ経由
- ビューへの射影を
lib/view/に集約する。 コンポーネントにstatus === 'doing'のような分岐を書かない(VIEW-02) - 自然文解析を
lib/parse/に集約し、純粋関数とする(PRS-05) - 繰り返し生成を
lib/recur/に集約し、純粋関数とする(FR-510) lib/parse/lib/view/lib/recur/lib/graph/lib/metrics/lib/search/は純粋関数のみ。 ストア・DOM に依存させず、new Date()を内部で呼ばない- スコープ適用(
_scope.ts)を全メソッドが必ず通す - 非公開プロジェクトを、検索インデックスの構築段階で除外する(SCP-04, FR-313)
- 派生値(ビュー射影・進捗率・負荷・リードタイム)はストアに保存せず算出する
- タスクの追加・D&D・ビュー切替・絞り込みに擬似ディレイを入れない(FR-115, FR-204)
- D&Dは楽観的更新。 失敗時のみ元の位置に戻す(VIEW-06, FR-211)
8. デザイン要件
アートディレクション
「カードが主役。3つの見え方を、同じカードで往復する」
TSUMU の中心にあるのはカードである。ボードでは列に並び、カレンダーでは日付に置かれ、リストでは行になる。同じタスクが姿を変えて現れることが、このアプリの体験の核。
だから TSUMU のカードは厚みを持つ。角丸12px、わずかな影、掴んだときに持ち上がる。ドラッグの手触りが、このアプリの品質そのもの。
- カードに厚みを与える。 姉妹プロジェクトで唯一、影を積極的に使う
- 生成りの紙色。 付箋・カードの手触りを想起させる、わずかに黄みのある白
- 色は分類のためにある。 プロジェクト色とステータス色。ただしカードを色で埋め尽くさない(左端の帯のみ)
- ドラッグ中は物理的に。 3度傾き、影が濃くなり、置いた場所に吸い付く
姉妹プロジェクトとの差別化(最重要) RELATE / KURA / TOKIWA は「表を読ませる」ので影を使わない。TENKEN は「濃い地に巨大ボタン」。HIBI は「1カラムの文章」。 TSUMU だけがカードに影を落とし、掴んで動かすことを前提にしている。 配色も分ける:RELATE=藍/ KURA=ティール+アンバー/ TOKIWA=インディゴ+コーラル/ HIBI=墨+若草/ TENKEN=濃い地+安全色/ TSUMU=生成りの紙色+インディゴ。
カラートークン
:root {
/* 生成りの紙色。付箋・カードの手触り */
--paper: #F6F4EF; /* ページ背景(ボードの地) */
--card: #FFFFFF; /* カード */
--card-alt: #FCFBF8; /* 列の背景 */
--line: #E4E0D6; /* 罫線 */
--line-soft: #EFEBE2;
--ink-900: #1A1C21; /* 見出し・本文 */
--ink-600: #565A63; /* 補助テキスト */
--ink-400: #93979F; /* ラベル・非活性 */
--primary: #3B4B9E; /* 主要CTA・選択状態(インディゴ) */
--primary-d: #2C3A7D;
--primary-50: #EAECF7;
/* ★ 期限の状態。これ以外の色を期限に使わない */
--due-overdue: #B23A32; /* 期限超過 */
--due-today: #C07C1E; /* 今日 */
--due-soon: #3B4B9E; /* 3日以内 */
--due-later: #93979F; /* それ以降(無彩色) */
/* 優先度(★ 控えめに。左端の細い帯のみ) */
--pri-urgent: #B23A32;
--pri-high: #C07C1E;
--pri-normal: transparent; /* ★ 通常は色を持たない */
--pri-low: #93979F;
/* 状態 */
--done: #3F7A5C;
--blocked: #8A5A9E; /* 依存でブロック中 */
--stalled: #C07C1E; /* 停滞 */
--wip-over: #B23A32; /* WIP上限超過 */
/* ★ カードの影(姉妹プロジェクトで唯一、影を使う) */
--shadow-card: 0 1px 2px rgba(26,28,33,.06), 0 1px 3px rgba(26,28,33,.04);
--shadow-hover: 0 2px 6px rgba(26,28,33,.10);
--shadow-drag: 0 12px 28px rgba(26,28,33,.22); /* ★ 掴んだとき */
}
配色ルール
- カードを色で塗り分けない。 プロジェクト色・優先度は**左端の細い帯(3px)**でのみ表現する
- 通常の優先度は色を持たない(
--pri-normal: transparent)。色がついているのは「急ぐもの」だけ - 期限の色を4段階に固定する。 超過=赤/今日=琥珀/3日以内=インディゴ/それ以降=無彩色
- 完了したカードは彩度を落とし、打ち消し線を入れる。 消さずに残すが、目立たせない
タイポグラフィ
| 役割 | 書体 | 用途 |
|---|---|---|
| UI全般 | Noto Sans JP 400 / 700 | 見出しも本文もこれ1つ |
| 日付・数値 | Roboto Mono 500(tabular-nums) |
期限、件数、進捗率、秒数 |
| トークン | サイズ | 用途 |
|---|---|---|
| page-title | 22px / 700 | 画面タイトル |
| column-title | 13px / 700(letter-spacing:.05em) |
ボードの列名 |
| card-title | 14px / 500・行間1.6 | カードのタイトル。2行でクランプ |
| task-title | 18px / 700 | タスク詳細のタイトル |
| body | 14px / 400 | 説明・コメント |
| meta | 12px / 400 | 補助情報 |
| due | 12px / 500 Mono | 期限の表示 |
| count | 12px / 500 Mono | 列の件数、進捗率 |
| add-seconds | 12px / 500 Mono | 追加秒数(控えめに) |
カードのタイトルは14px・行間1.6・2行クランプ。 ボードで一覧するときの可読性と、1列に収まる枚数のバランスがここで決まる。
レイアウトとスペーシング
| 項目 | 定義 |
|---|---|
| スペーシング | 4pxベース:4 / 8 / 12 / 16 / 24 / 32 / 48 |
| アプリシェル | 左サイドバー(240px、折りたたみ56px)+ ヘッダー(52px)+ メイン |
| ボードの列 | 幅280px、間隔16px。横スクロール。列ヘッダーは固定 |
| カード | 幅は列いっぱい、最小高さ64px。内側 12px、カード間 8px |
| カレンダーのセル | 月表示:最小高さ120px。週表示:時間軸なし(終日扱い) |
| リストの行高 | 標準40px / コンパクト32px |
| タイムラインの行 | 高さ36px、1日=32px(週表示)/8px(月表示) |
| 角丸 | 12px(カード)/8px(列・パネル)/6px(ボタン・入力)/999px(チップ・アバター) |
| 影 | カードのみ(--shadow-card)。ホバーで --shadow-hover、ドラッグで --shadow-drag |
| クイック追加欄 | 各ビューの上部に常設。高さ44px |
| セーフエリア | env(safe-area-inset-bottom) を考慮 |
主要コンポーネント仕様
| コンポーネント | 仕様 |
|---|---|
| タスクカード(最重要) | 左端3pxにプロジェクト色(優先度が urgent/high なら優先度色を優先)/タイトル(card-title・2行クランプ)/下段に 期限(due・色分け)・担当アバター・サブタスク進捗(3/5)・タグ数・コメント数。ホバーで影が濃くなる |
| ドラッグ中のカード | 3度傾き、--shadow-drag、不透明度95%。 元の位置にはプレースホルダ(点線の枠) |
| ドロップ先の表示 | 挿入位置に2pxのインディゴのライン。列全体をハイライトしない(うるさくなる) |
| ボードの列 | ヘッダーに 列名(column-title)・件数(count)・WIP上限。上限超過で件数が --wip-over。列の背景は --card-alt |
| クイック追加欄 | 各ビュー上部。プレースホルダに例文。入力すると下に解析チップが現れる |
| 解析チップ | アイコン + ラベル + ×。種別で色を変えない(インディゴ系で統一)。× でタイトルに戻る |
| 記法ヒント | 入力欄の直下に --ink-400 で常時表示。「明日/来週金曜/@名前/#タグ」(PRS-06) |
| 追加秒数 | 追加後に入力欄の右に控えめに 2.4秒 と表示、3秒で消える |
| カレンダーのセル | 日付(Mono)+ カード(簡易表示:色帯 + タイトル1行)。4件を超えたら「他N件」 |
| 未スケジュールのサイドバー | カレンダー右側。期限未設定のタスク。日付にドラッグして期限を設定 |
| タイムラインの横棒 | プロジェクト色。依存はカーブした線で結ぶ。ドラッグで期間変更 |
| 依存の警告 | 着手時にモーダル。「『要件ヒアリング』が未完了です」+ 前提タスクへのリンク + 「それでも進める」ボタン |
| サブタスク | チェックリスト。進捗率を親カードにバーで表示(高さ2px、カード下端) |
| 完了カード | 彩度を落とし、タイトルに打ち消し線。5秒間「元に戻す」トースト |
| 負荷の可視化 | 担当者別の横棒(タスク数)+ 期限超過数のバッジ。平均線を引き、大きく外れた人を強調 |
| 要対応リスト | 種別ごとにセクション(期限超過/停滞/未割当/ブロック中)。件数と経過日数(Mono) |
| 模擬チャット | 実際のチャット風。上部に「これは模擬チャットです」の帯。各メッセージに「タスクにする」ボタン |
| コマンドパレット | ⌘K。上部に入力、下に候補(検索結果/アクション/ビュー)。「新しいタスク:〜」が常に先頭候補 |
| 空状態 | 「タスクがありません」/「条件に一致するタスクはありません」を出し分け |
| スケルトン | カード形状。列の高さを確保してレイアウトシフトを防ぐ |
| デモ切替バー | 濃い墨色の帯。生成りの明るいアプリ本体と区別する |
インタラクション要件(ドラッグの手触りが命)
| ID | 要件 |
|---|---|
| IX-01 | ドラッグ中のフレームレートが60fpsを維持すること。 カクついたら実装を見直すこと |
| IX-02 | ドラッグ開始の判定は移動距離5px。 クリックとドラッグを誤認しないこと |
| IX-03 | ドロップ位置のプレビュー(挿入ライン)を常に表示すること |
| IX-04 | ドラッグ中に列の端に近づくと自動スクロールすること(速度は距離に比例) |
| IX-05 | D&Dをキーボードでも行えること(Space で掴む → 矢印で移動 → Space で置く → Esc で取消) |
| IX-06 | タスクの追加が100ms以下で、その場にカードが現れること |
| IX-07 | ビュー切替が100ms以下。データを取り直さないこと |
| IX-08 | ⌘K コマンドパレット、N 新規タスク、/ 検索、? ショートカット一覧 |
| IX-09 | カード上で E 編集、Space 完了トグル、Del 削除 |
| IX-10 | 完了・削除は5秒間「元に戻す」を表示すること |
| IX-11 | 更新は楽観的。失敗時のみ元に戻し、エラーを表示すること |
| IX-12 | モーダルを開いても背後のビューの状態(スクロール位置・絞り込み)が保持されること |
モーション
| 対象 | duration | 内容 |
|---|---|---|
| カードを掴む | 120ms | 3度傾き + 影が --shadow-drag に + わずかに拡大(1.02) |
| カードを置く | 200ms | 傾きが戻り、置いた位置に吸い付く。cubic-bezier(.2,.9,.3,1) |
| カードの移動(他人の操作/ロール切替後) | 300ms | 位置の変化をアニメーションで見せる(FLIP) |
| ホバー | 100ms | 影の変化のみ。移動しない |
| ビュー切替 | 180ms | フェード + 8px。カードの位置をアニメーションさせない(混乱するため) |
| 解析チップの出現 | 140ms | 下からポップ |
| 完了 | 200ms | 彩度が落ち、打ち消し線が引かれる |
| 繰り返しの次回生成 | 300ms | トースト + カレンダー上で新カードがフェードイン |
| トースト | 160ms | 下から |
prefers-reduced-motion: reduce 時は、傾き・拡大・FLIPアニメーションを停止し、位置変化を即時にする。 ただしドロップ位置の挿入ラインは残す(操作の手がかりとして必要)。
アクセシビリティ(WCAG 2.1 AA)
| ID | 要件 |
|---|---|
| A11Y-01 | コントラスト比は通常4.5:1以上、18px以上は3:1以上 |
| A11Y-02 | 全ての操作をキーボードのみで完遂できること |
| A11Y-03 | D&Dにキーボード代替を用意すること(IX-05)。移動先の候補を読み上げること |
| A11Y-04 | ボードを role="list" / role="listitem" で構造化し、列名を aria-label で示すこと |
| A11Y-05 | カードに、内容を要約したアクセシブルな名前を持たせること(「A社向け提案書の作成、期限 9月4日、担当 田中、進行中」) |
| A11Y-06 | 期限の状態を色のみで伝えないこと。 「期限超過」「今日」のテキストを併記すること |
| A11Y-07 | 優先度・ステータスを色のみで伝えないこと |
| A11Y-08 | フォーカスリングは2px・オフセット2pxで常時可視 |
| A11Y-09 | カードの移動を aria-live="polite" で通知すること(「進行中に移動しました」) |
| A11Y-10 | 解析チップの内容を aria-live="polite" で通知すること(「期限 9月4日、担当 田中 を認識しました」) |
| A11Y-11 | フォームの各入力に label を関連付け、エラーは aria-describedby と role="alert" で読み上げること |
| A11Y-12 | モーダルはフォーカストラップ + Esc で閉じる + 起動元へフォーカス復帰 |
| A11Y-13 | タップ領域は最小44×44px |
| A11Y-14 | カレンダーを表としても読めるようにマークアップすること |
| A11Y-15 | フォントサイズ200%指定でも、ボードとリストが操作できること |
ライティング規約
| 原則 | 例 |
|---|---|
| 解析結果を説明する | ○「期限 9/4(金)・担当 田中 として追加します」/× チップだけ出して説明しない |
| 解析できなくても止めない | ○「そのまま追加できます」/×「日付を認識できませんでした」 |
| 依存の警告は選択肢を残す | ○「『要件ヒアリング』が未完了です。それでも進めますか?」/×「前提タスクが未完了のため着手できません」 |
| 繰り返しの生成を伝える | ○「次回分を 9/25 に作成しました」/× 黙って作る |
| 期限は相対で | ○「明日」「3日後」「2日超過」/×「2026-09-04」だけ |
| 負荷は事実だけ | ○「田中さんが12件(平均5件)」/×「田中さんの負荷が高すぎます」 |
| 一括操作は件数を明示 | ○「12件の担当を変更しますか?」/×「変更しますか?」 |
| 空状態を区別する | データ0件:「まだタスクがありません。上の欄から追加できます」/絞り込み0件:「条件に一致するタスクはありません」+ 条件解除 |
| システム語を使わない | ○「このタスクを完了にする」/×「statusをdoneに更新」 |
9. 技術要件・ディレクトリ構成
技術スタック(固定・勝手に変更しない)
| レイヤ | 技術 | 備考 |
|---|---|---|
| フレームワーク | Next.js 15(App Router)/ TypeScript strict | |
| スタイリング | Tailwind CSS + CSS Variables | トークンは CSS 変数で定義 |
| UIコンポーネント | shadcn/ui(Radix UI基盤) | |
| 状態管理 | Zustand + persist | localStorage に永続化 |
| D&D | dnd-kit | キーボード対応が必須のため。 react-beautiful-dnd を使わない |
| ボード・カレンダー・タイムライン | 自前実装 | FullCalendar / react-big-calendar / ガントライブラリを使わない。 3ビュー同期は既製品では作れない |
| 自然文解析 | 自前実装(lib/parse/) |
chrono-node 等を入れない。 日本語の相対日付は自前で書ける。これが記事の技術パートの見せ場 |
| 仮想スクロール | TanStack Virtual | リストビュー |
| テーブル | TanStack Table v8 | リストビューのヘッドレス。UIは自前 |
| 全文検索 | 自前実装(lib/search/) |
Fuse.js を入れない |
| フォーム | React Hook Form + Zod | 詳細フォーム・設定画面のみ |
| コマンドパレット | cmdk | ⌘K |
| グラフ | Recharts(動的import) | 分析画面のみ |
| CSV | Papa Parse | |
| 画像 | IndexedDB(idb)+ Canvas で圧縮 | 添付ファイル |
| 日付 | date-fns(ja locale) | タイムゾーンは Asia/Tokyo 固定 |
| PWA | next-pwa | |
| アイコン | lucide-react | |
| ホスティング | Vercel | |
| テスト | Vitest / Playwright / axe-core |
上記以外のライブラリを入れる前に必ず提案し、承認を得ること。特にカレンダーライブラリ、ガントライブラリ、日付解析ライブラリ、全文検索ライブラリの導入は禁止。
カレンダー・ガントを自前で書く理由: 既製ライブラリは自前のデータモデルを持っており、3ビュー同期(VIEW-01)と噛み合わない。さらにD&Dの挙動をライブラリ側に握られると、IX-01(60fps)とIX-05(キーボード代替)を保証できない。自前実装であることがこのデモの価値。
ディレクトリ構成
/
├── CLAUDE.md
├── app/
│ ├── layout.tsx # アプリシェル(サイドバー・ヘッダー・デモ切替バー・⌘K)
│ ├── page.tsx # SC-001 今日 / SC-900(?embed=1)
│ ├── board/ list/ calendar/ timeline/ # SC-010〜013
│ ├── tasks/[id]/ # SC-020
│ ├── inbox/ # SC-030, 031
│ ├── import/ search/ notifications/ # SC-032, 040, 041
│ ├── how-it-works/ # SC-050
│ ├── team/ # SC-100〜103
│ ├── projects/ # SC-110, 111
│ ├── admin/ # SC-120〜160
│ └── dev/components/
├── components/
│ ├── card/ # ★ 最重要
│ │ ├── TaskCard.tsx # カード(左帯・タイトル・期限・担当・進捗)
│ │ ├── DragPreview.tsx # ドラッグ中の見た目
│ │ ├── DropIndicator.tsx # 挿入ライン
│ │ └── useCardDnd.ts # dnd-kit のラッパー(キーボード対応込み)
│ ├── views/
│ │ ├── BoardView.tsx BoardColumn.tsx
│ │ ├── ListView.tsx
│ │ ├── CalendarView.tsx CalendarCell.tsx UnscheduledPanel.tsx
│ │ ├── TimelineView.tsx DependencyLines.tsx
│ │ └── ViewToolbar.tsx # ★ ビュー切替 + 絞り込み(状態を維持)
│ ├── quick-add/ # ★ QuickAddInput, ParseChips, SyntaxHint, AddSeconds
│ ├── task/ # SubtaskList, DependencyPanel, CommentList, HistoryList
│ ├── team/ # LoadChart, AttentionList, TeamBoard
│ ├── mock/ # MockChatPanel(★ 模擬である旨の帯を含む)
│ ├── demo/ # RoleSwitcher, ResetButton, GuideTour, BackToArticle
│ └── layout/
├── lib/
│ ├── types/ seed/ store/ repo/
│ ├── parse/ # ★ 自然文解析。純粋関数
│ ├── view/ # ★ 3ビュー射影。純粋関数
│ ├── recur/ # ★ 繰り返し生成。純粋関数
│ ├── graph/ # ★ 依存関係。純粋関数
│ ├── metrics/ search/ csv/ storage/ query/ validation/ utils/
├── docs/
│ └── build-or-buy-checklist.md # 資料PDFの元原稿(自作判断シート)
├── e2e/
└── public/
コーディング規約
- TypeScript strict。
any禁止。やむを得ない場合はunknown+ 型ガード - ファイル名は kebab-case、コンポーネントは PascalCase
- 1ファイル300行を超えたら分割を検討する
- コンポーネントから Zustand ストアを直接参照しない。必ず
lib/repo/経由 - ビューへの射影を
lib/view/に集約する。 コンポーネントにステータス判定を書かない(VIEW-02) lib/parse/lib/view/lib/recur/lib/graph/lib/metrics/lib/search/は純粋関数のみ。 ストア・DOM に依存させず、new Date()を内部で呼ばない- D&D の状態を Zustand に持たせない。 dnd-kit のコンテキスト内で完結させ、確定時のみリポジトリを呼ぶ(60fps を守るため)
- ドラッグ中に再レンダリングを起こす state 更新をしない
- タスクの追加・D&D・ビュー切替に擬似ディレイを入れない
- 派生値(ビュー射影・進捗率・負荷・リードタイム)をストアに保存しない
- 非公開プロジェクトを、検索インデックスの構築段階で除外する(SCP-04)
- コミットは1タスクごと。メッセージに要件IDを含める
例:
feat(parse): 相対日付「来週金曜」の解決を実装 (FR-103)
10. 非機能要件
| ID | 要件 | 目標値 |
|---|---|---|
| NFR-01 | ドラッグ中のフレームレート | 60fps維持 |
| NFR-02 | タスクの追加から画面反映まで | 100ms以下 |
| NFR-03 | ビュー切替(ボード↔リスト↔カレンダー) | 100ms以下 |
| NFR-04 | 絞り込み・並び替えの反映 | 100ms以下 |
| NFR-05 | 自然文解析(入力1文字ごと) | 16ms以下(1フレーム以内。入力を妨げない) |
| NFR-06 | 全文検索(タスク1,200件 + コメント900件) | 100ms以下 |
| NFR-07 | ボード(列5・カード300枚)の初期描画 | 300ms以下 |
| NFR-08 | カレンダー月表示(カード400枚)の描画 | 300ms以下 |
| NFR-09 | リスト1,200行のスクロール | 60fps維持(仮想スクロール) |
| NFR-10 | LCP(デスクトップ) | 1.8秒以下 |
| NFR-11 | INP | 200ms以下 |
| NFR-12 | CLS | 0.1以下。カードとカレンダーセルの高さを最初から確保すること |
| NFR-13 | 埋め込みモードの初期JSバンドル(gzip後) | 65KB以下 |
| NFR-14 | アプリ本体の初期JSバンドル(gzip後) | 230KB以下(グラフ・CSVは動的import) |
| NFR-15 | Lighthouse | Performance 92 / Accessibility 95 以上 |
| NFR-16 | 対応環境 | Chrome / Safari / Edge 最新2バージョン、iOS Safari 16以降、Android Chrome 最新 |
| NFR-17 | カードを100回連続でドラッグしてもメモリが単調増加しないこと | — |
| NFR-18 | localStorage の容量上限に達した場合、警告を表示し、完了済みの古いタスクをアーカイブして圧縮すること(データを破損させない) | — |
セキュリティ・プライバシー
| ID | 要件 |
|---|---|
| SEC-01 | タスクのタイトル・説明・コメント・添付を一切外部に送信しないこと。 localStorage / IndexedDB にのみ保存すること |
| SEC-02 | この事実を、クイック追加欄の近くとデモ切替バーで明示すること |
| SEC-03 | GA4 にタスクの内容を送らないこと(EMB-11)。送るのは秒数・解析ヒット種別などのメタ情報のみ |
| SEC-04 | ユーザー入力(タイトル・説明・コメント)をサニタイズすること |
| SEC-05 | CSVインポート時の数式インジェクションを無害化すること(FR-120) |
| SEC-06 | 「デモをリセット」ですべてのデータが消えることを明示し、実際に消えること |
| SEC-07 | 非公開プロジェクトのタスクが、検索を含むあらゆる経路でメンバー以外に返らないことをテストで保証すること(SCP-04) |
運用設計上の注意(この領域固有)
タスク管理システムは容易に監視ツールになる。 HIBI(日報)と同じ配慮が必要。
| ID | 要件 |
|---|---|
| ETH-01 | 完了数・リードタイムの個人別ランキングを実装しないこと(FR-713) |
| ETH-02 | 負荷の可視化を「働いていない人を見つける」ためではなく「偏りを直す」ために提示すること。 文言でそれを示すこと |
| ETH-03 | 停滞タスクの検出を、担当者への非難として提示しないこと。 「7日動いていません」という事実に留める |
| ETH-04 | 見積時間・実績時間の入力を必須にしないこと(FR-411)。強制すると数字が嘘になる |
| ETH-05 | 実績時間を勤怠・評価に流用する機能を実装しないこと |
実案件への転用時の注意(デモ本体の要件ではない。商談で聞かれた際に説明できるよう記録)
① タスク管理は「入力される仕組み」とセットでないと死ぬ。 チャット連携・メール転送・テンプレート・繰り返し——入力の摩擦を下げる仕組みへの投資が、機能追加より効果が高い
② 既製ツールとの併用が現実解になることが多い。 「全社の標準ツールは既製、特定業務だけ自作」という構成は合理的。全部を置き換えようとしない
③ 移行時にデータの持ち出しやすさが問われる。 既製ツールから移すとき、逆に将来また移すとき。CSVエクスポートを最初から持たせておく
④ 非公開プロジェクトの権限設計は、実装前に確定させる。 人事・M&A・懲戒などの案件が混ざると、検索から漏れただけで事故になる
⑤ 通知の設計を誤ると、通知疲れで使われなくなる。 「全部通知する」から始めず、必要最小限から足していく
本デモはデータをサーバーに送信しないため、③④の義務は発生しない。「デモである」旨は画面上に常時明示する
11. 費用設計
記事の主題の半分は「費用」。この章は実装要件であると同時に、記事本文の原稿素材でもある。
タスク管理だけの特殊事情:無料で使える優れた既製品が山ほどある
他のどのテーマよりも、「作らない」選択肢が強い領域。無料枠で十分に使えるツールが複数ある。
だから、この記事で最初に答えるべきは「いくらか」ではなく——
「タスク管理システムを、そもそも自作する必要があるのか」
この問いに正直に答えることが、結果的に信頼を生む。 そして多くの場合、答えは「既製ツールを使うべき」になる。
まず、作らない選択肢を並べる
| 手段 | 向くケース | 限界 |
|---|---|---|
| 既製ツールの無料枠 | 10名以下・機能要求が標準的 | 人数・機能・履歴に制限。多くの会社はこれで足りる |
| 既製ツールの有料プラン | 30〜300名。標準的な運用 | 月額×人数。カスタマイズに限界 |
| スプレッドシート | 5名以下・シンプルな一覧 | ビューが1つ。通知がない。同時編集で壊れる |
| チャットのタスク機能 | 依頼がチャット中心 | 一覧性が低い。期限管理が弱い |
| 自作 | 下記の①〜⑤に該当する場合 | 開発費 + 保守 + 既製ツールに機能で追いつけない |
「既製ツールの無料枠から始めて、限界が来たら考える」が最も合理的なケースが多い。 記事にはこれを明記する。
自作が正当化される5つのケース
| ケース | 内容 |
|---|---|
| ① 業務フローが特殊で、既製ツールの概念に載らない | 独自のステータス遷移、承認の分岐、業種特有の項目(工事の工程・施術の順序など) |
| ② 既存システムと一体化したい | 顧客管理・案件管理・在庫・勤怠とタスクを繋ぎ、二重入力をなくしたい |
| ③ 自社の別システムにタスク機能を組み込みたい | 独立したタスクツールではなく、既存の業務システムの中にタスクが必要 |
| ④ セキュリティ要件で外部SaaSが使えない | 業種・取引先要求により、業務データを外部に置けない |
| ⑤ 人数が非常に多く、人数課金が重い | 数百名規模で、月額×人数が積み上がっている |
②③が、自作の最も現実的な動機。 「タスク管理ツールを作りたい」ではなく、**「うちの業務システムに、タスクの概念を足したい」**という形で来ることが多い。
逆に、①〜⑤のどれにも当てはまらないなら、既製ツールを使うべき。 機能数では絶対に勝てない。
記事に書くべき一文: 「タスク管理システムの自作は、既製ツールに勝つためではありません。既製ツールでは繋げないものを繋ぐためです。」
損益分岐の計算式
既製ツールの年間コスト
= 月額単価 × 人数 × 12
( + オプション:ゲスト・自動化回数・ストレージ )
回収年数 = (開発費 + 運用設計費)÷(既製ツール年間コスト − 自作の年間運用費 − 年間保守費)
| 規模 | 人数 | 判断 |
|---|---|---|
| 小 | 〜15名 | 無料枠で十分。自作は回収できない |
| 中 | 30〜100名 | ①〜④に該当するなら検討価値あり。⑤だけでは弱い |
| 大 | 300名超 | 人数課金が効いてくる。ただし要求も複雑化する |
ただし②③のケースでは、この計算式が意味を持たない。 既製ツールでは実現できないことをやるのだから、**比較対象は「既製ツール」ではなく「二重入力を続けるコスト」**になる。
二重入力を続けるコスト
= 1件あたりの転記時間 × 月間件数 × 12 × 時間単価 × 人数
この計算の方が、②③の案件では説得力がある。
最も見落とされる費用:機能追加の圧力
タスク管理は「あのツールのあの機能が欲しい」が最も出やすい領域。 そして——
- ガントチャートが欲しい
- 自動化ルールが欲しい
- ダッシュボードが欲しい
- モバイルアプリが欲しい
- AIで優先度を提案してほしい
これを1つずつ足していくと、既製ツールの劣化コピーになって、しかも保守費だけが積み上がる。
記事に書くべき一文: 「自作したタスク管理システムに機能を足したくなったら、それは既製ツールに戻るべきサインかもしれません。」
TSUMU が3章のスコープリスクで「既製ツールの機能を真似しない」を明記しているのは、この圧力に対する防波堤。 資料PDFの「自作判断シート」も、この会議のために作る。
AI連携は「あとで足す」でよい
「AIでタスクを自動分解したい」「優先度を提案してほしい」は必ず出る要望。だが——
| AI連携なし(本デモ) | AI連携あり | |
|---|---|---|
| 自然文からの解析 | ルールベースで十分な精度(PRS-01) | 曖昧な表現にも対応 |
| 費用 | ゼロ | 1件ごとに課金。タスク追加数 × 人数 で積み上がる |
| データの扱い | 外に出ない | タスクの内容を外部APIに送る(④のセキュリティ要件と衝突する) |
| 実装 | 数百行 | プロキシ(バックエンド)が必要 |
| 速度 | 16ms以下(入力中にリアルタイム解析) | 往復で数百ms〜。入力の邪魔になる |
特に速度が問題。 入力中のリアルタイム解析はAPIでは実現できない。ルールベースの方が、この用途では優れている。
Webで足りるか、ネイティブが必要か
タスク管理は、Webで足りる領域。ただしプッシュ通知が論点になる。
| やりたいこと | Web(PWA) | ネイティブ |
|---|---|---|
| タスクの追加・確認・完了 | できる | できる |
| ボード・カレンダーの操作 | できる(PCが主) | できる |
| オフラインでの閲覧・追加 | できる(PWA) | できる |
| 期限前のプッシュ通知 | 限定的 | できる |
| 共有シートからの追加(他アプリから) | 限定的 | できる |
判断の目安: 期限通知を重視するなら、チャットツール・LINE公式アカウント・メールへの通知が現実的な代替(アプリ開発より圧倒的に安い)。タスク管理のためにネイティブアプリを作る必要はほぼない。
デモ自体の運用コストを実測して記事に載せる
| ID | 要件 |
|---|---|
| COST-01 | デモ公開後、月次で「デモ起動数・タスク追加数・ホスティング費用」を記録すること |
| COST-02 | add_seconds の分布を集計し、記事に掲載すること。「読者の中央値は何秒でタスクを積めたか」は、主張を裏付けるデータになる(EMB-10) |
| COST-03 | parse_hit の内訳を記録すること(期限・担当・タグのどれが最も解析されたか)。実装の優先度判断に使える実データになる |
| COST-04 | 記録した実測値を記事に掲載し、確認日を明記すること |
| COST-05 | 既製ツール・AI APIの価格は変動するため、この文書に金額を書き込まない。 記事執筆時点で公式ページを確認し、確認日を明記すること |
12. 実装タスク
この順に進める。フェーズを飛ばさない。 完了時は [x] に更新する。
Phase 0 — 基盤と純粋ロジック(4日)
0-1. 初期化
- Next.js 15 / TypeScript strict / App Router / Tailwind で初期化
- ESLint / Prettier / husky(pre-commit で lint + typecheck)
- 9章のディレクトリ構成を作成
0-2. lib/parse/(記事の技術パートの見せ場)
-
date.ts:相対日付の解決(今日/明日/明後日/今週◯曜/来週◯曜/N日後/月末/M/D)(FR-103) -
member.ts:@名前と氏名・別名のあいまい一致(FR-104) -
recurrence.ts:「毎週月曜」「毎月25日」「毎営業日」の解析(FR-107) -
quick-add.ts:統合(7章のコード参照)(FR-102, PRS-01〜06) - 解析語をタイトルから取り除き、
rawTextを保持すること(FR-109) -
new Date()を内部で呼ばない。現在時刻は引数で受け取る - 単体テストを網羅的に書く。以下を必ず含めること:
- 「来週金曜」が正しい日付に解決されること(週の境界、月跨ぎ)
- 「田中さん」でメンバーが解析されること(別名一致)
- 解析できない文字列でも title が返ること(PRS-04・最重要)
- 解析語がタイトルから取り除かれること
-
rawTextからタイトルに戻せること(FR-109) - 複数の解析が同時に効くこと(期限 + 担当 + タグ)
- 解析が 16ms以下で完了すること(NFR-05)
0-3. lib/view/ lib/recur/ lib/graph/(純粋関数)
-
view/board.ts:ステータス別の列(7章のコード参照)(FR-201, FR-212) -
view/calendar.ts:日付別の配置 + 未スケジュールの抽出(FR-213) -
view/list.ts/view/timeline.ts -
view/filter.ts:絞り込み・並び替え・グループ化 -
recur/next.ts:次回日付の算出(6種のルール)(FR-501) -
recur/holidays.ts:営業日判定(FR-508) -
recur/generate.ts:次回タスクの生成(FR-502, FR-510) -
graph/cycle.ts:依存の循環検出(FR-406) -
graph/blocked.ts:ブロック中の抽出(FR-407) -
graph/progress.ts:サブタスクからの進捗率(FR-402) - 単体テストを網羅的に書く。以下を必ず含めること:
- 同一の Task から3ビューが正しく射影されること(VIEW-01)
- WIP上限の超過判定(FR-212)
- 期限未設定のタスクがカレンダーの未スケジュールに入ること
- 繰り返しの次回日付が6種すべて正しいこと(月末・うるう年・営業日の祝日跨ぎ)
- 依存の循環が検出されること(3階層以上でも)
- サブタスクの進捗率が正しいこと(0件のときに壊れないこと)
0-4. lib/metrics/ lib/search/
-
metrics/load.ts:担当者別の負荷と平均からの偏差(FR-603, FR-604) -
metrics/attention.ts:期限超過・停滞・未割当・ブロック中(FR-605) -
metrics/leadtime.ts:リードタイム(個人別ランキングを作らない)(FR-711, FR-713) -
search/:転置インデックス + 日本語正規化 + ハイライト -
search/index.tsで、非公開プロジェクトをインデックス構築段階から除外すること(SCP-04, FR-313) - 単体テストを書く。特に非公開プロジェクトが検索に出ないこと
0-5. 型とシード
-
lib/types/に7章の型定義をすべて実装 -
lib/seed/の全ファイル - タスクのタイトルを、業種の世界観を持つ実在しそうなものにする。ダミー文を1件も残さない(最重要)
- メンバーに別名を持たせる(「田中さん」で解析できるように)
- 非公開プロジェクトを2件、期限超過・停滞・未割当・担当の偏りを作る
- 依存関係を10組以上、うち数組は前提が未完了のまま進行中
- WIP上限を超えている列を1つ作る
- 模擬チャットに、タスク化しやすいメッセージと雑談を混在させる
- 期限の分布に濃淡(今日・今週に集中、来月は疎)
- 日付は現在日時からの相対で生成する
0-6. ストアとリポジトリ
-
lib/store/{session,data,settings,view,ui}.tsを Zustand + persist で実装 -
lib/repo/_scope.tsを最初に実装する(SCP-01〜05) -
lib/repo/の全モジュール。全メソッド async、Result型、スコープ適用 - スコープの単体テストをこの時点で書く:
- 担当者が未所属プロジェクトのタスクを取得できないこと(SCP-03)
- 非公開プロジェクトが、検索を含むあらゆる経路で返らないこと(SCP-04, SEC-07)
- 見積時間が権限なしで返らないこと(SCP-05)
- 追加・D&D・ビュー切替に擬似ディレイを入れない
0-7. デザインシステム
-
app/globals.cssにカラートークンを CSS 変数で定義(カードの影3段階を含む) -
tailwind.config.tsから CSS 変数を参照するよう theme を拡張 - フォント(Noto Sans JP / Roboto Mono)を
next/fontで最適化。期限・件数にtabular-nums - shadcn/ui を導入し、トークンに合わせて上書き
- アプリシェル(サイドバー折りたたみ、ヘッダー)
-
TaskCard.tsx:左帯・タイトル2行クランプ・期限の色分け・担当・進捗バー(8章) - カードに
aria-label(内容の要約)を持たせる(A11Y-05) - 共通コンポーネント:
EmptyState(2種の出し分け)/Skeleton(カード形状・列の高さを確保) -
prefers-reduced-motionの対応(挿入ラインは残す) -
/dev/componentsにコンポーネントカタログを作成
0-8. デモ基盤
-
RoleSwitcher(3ロール + 担当者の3ペルソナ選択)(FR-806) - デモ切替バー(濃い墨色の帯)+ 「これはデモです。タスクの内容は送信されません」(SEC-02)
- 「デモをリセット」(FR-810)/ロール権限外アクセス時の案内画面(FR-813)
- 元記事リンク・資料ダウンロード(FR-815)
Phase 0 完了チェック
-
lib/parse/lib/view/lib/recur/lib/graph/のテストが全パターン通る - 「解析できなくても title が返る」がテストで検証済み
- 「同一 Task から3ビューが正しく射影される」がテストで検証済み
- 「非公開プロジェクトが検索に出ない」がテストで検証済み
- 自然文解析が16ms以下
-
/dev/componentsでカードが全パターン正しく表示される
Phase 1 — 入力体験(3日)
-
QuickAddInput.tsx:1行入力、Enter で確定、確認ダイアログなし(FR-101) -
ParseChips.tsx:打っている最中に解析チップを表示(FR-108, PRS-02) - チップの
×で解析を取り消し、タイトルに戻す(FR-109, PRS-03) -
SyntaxHint.tsx:記法のヒントを入力欄の直下に常時表示(FR-111, PRS-06) - 解析できなくても必ず追加できること(FR-110)
-
aria-liveでの解析結果の通知(A11Y-10) -
AddSeconds.tsx:追加秒数を控えめに表示、3秒で消える(FR-112) - 追加が100ms以下で、その場にカードが現れること(FR-115, NFR-02)
-
⌘Kコマンドパレット(cmdk)から追加。画面遷移しない(FR-113) - 「新しいタスク:〜」が常に先頭候補になること
- 改行区切りの一括追加(FR-114)
- SC-023 詳細フォームでの追加(FR-116)
- SC-024 テンプレートからの追加:起点日の指定、日付の再計算、依存の生成(FR-117, FR-118)
- SC-032 CSVインポート(数式インジェクションの無害化)(FR-119, FR-120, SEC-05)
- ショートカット(
N新規、/検索、?一覧)(IX-08)
Phase 1 完了チェック
- 1行入力で期限・担当・タグが解析され、チップで見える
- 解析を取り消してタイトルに戻せる
- 解析できない入力でも追加できる
- 追加が100ms以下、解析が16ms以下
Phase 2 — 3ビューとD&D(5日・最重要フェーズ)
2-1. カードとD&D基盤
-
useCardDnd.ts:dnd-kit のラッパー(キーボード対応込み) -
DragPreview.tsx:3度傾き +--shadow-drag+ 拡大1.02(8章) -
DropIndicator.tsx:挿入位置に2pxのライン(IX-03) - ドラッグ開始判定は移動距離5px(IX-02)
- 端に近づいたときの自動スクロール(IX-04)
- キーボードでのD&D(
Space掴む → 矢印 →Space置く →Esc取消)(IX-05, A11Y-03) - 移動を
aria-liveで通知(A11Y-09) - D&D の状態を Zustand に持たせない。ドラッグ中に再レンダリングを起こさない(9章)
- 60fps を実測(NFR-01, IX-01)
2-2. 3ビュー
-
BoardView.tsx/BoardColumn.tsx:列幅280px、件数・WIP上限、横スクロール(FR-206, FR-212) -
ListView.tsx:TanStack Table ヘッドレス + 自前UI、仮想スクロール(FR-314) - 列カスタマイズ・グループ化(FR-307, FR-308)
-
CalendarView.tsx:月/週、日付へのD&Dで期限変更(FR-207, FR-214) -
UnscheduledPanel.tsx:期限未設定のサイドバー、日付にドラッグ(FR-213) -
TimelineView.tsx+DependencyLines.tsx:横棒と依存の線(FR-215, FR-216) -
ViewToolbar.tsx:ビュー切替 + 絞り込み。切り替えても絞り込みが維持されること(FR-203, VIEW-03) - ビュー切替が100ms以下。データを取り直さない(FR-204, NFR-03)
- どのビューからでも期限変更・担当変更・完了ができること(FR-205, VIEW-05)
- リストで期限を変えるとカレンダーに反映されること(FR-208)
- 楽観的更新とロールバック(FR-211)
- SC-001 今日(自分の今日やること)(F-V05)
2-3. 絞り込みと保存ビュー
- 絞り込み条件(FR-301)/URLクエリ同期(FR-302)/チップ表示(FR-303)
- 保存ビュー:絞り込み + ビュー種別 + グループ化 + 表示列(FR-304)
- サイドバーに一覧、件数バッジ、個人/共有の区別(FR-305, FR-306)
- 一括操作 + 確認ダイアログ(FR-309, FR-310)
- SC-040 検索(ハイライト、100ms以下)(FR-311, FR-312)
2-4. 埋め込みモード
- SC-900 埋め込みモード:クイック追加 + 3列の小ボード(FR-817, EMB-04〜08)
- サンプル入力例3つをタップで入力欄に(EMB-06)
- 追加秒数の表示(EMB-05)/カードのD&D(EMB-07)
- アプリ内遷移をしない(EMB-08)
- 初期バンドル 65KB以下を実測(NFR-13)
Phase 2 完了チェック
- フローAが完走する(埋め込みで追加 → D&D → 全画面 → 3ビュー往復)
- ドラッグが60fpsを維持し、引っかからない
- リストで期限を変えるとカレンダーが変わる。カレンダーで動かすとリストが変わる
- ビューを切り替えても絞り込みが維持される
- キーボードだけでカードを移動できる
- カードを100回ドラッグしてもメモリが単調増加しない
Phase 3 — タスクの中身・繰り返し・依存(4日)
3-1. タスク詳細
- SC-020 タスク詳細(全項目)(FR-401)
- サブタスク(チェックリスト)+ 親の進捗率への反映(FR-402)
- サブタスクの並び替え(キーボード対応)(FR-403)
- 依存関係の設定 + 循環の検出(FR-404, FR-406)
- 前提未完了での着手時に警告。ブロックはしない(FR-405)
- コメント + メンション(FR-408)
- 添付ファイル(IndexedDB、Canvas で圧縮)(FR-409)
- 完了を1タップ + 5秒の「元に戻す」(FR-410, IX-10)
- 見積/実績時間(必須にしない)(FR-411, ETH-04)
- 変更履歴(FR-412)/複製(FR-413)/削除 + 取り消し(FR-414)
- カード上のショートカット(
ESpaceDel)(IX-09)
3-2. 繰り返し
- 繰り返しルールの設定UI(6種)(FR-501)
- 完了時/期日到来時の次回生成(FR-502)
- 生成をトーストで通知(FR-503)
- 終了条件(FR-504)
- SC-120 繰り返しタスクの管理:一覧、次回生成日、停止・再開(FR-505, FR-506)
- 停止しても過去に生成されたタスクは消えないこと(FR-506)
- 生成履歴(FR-507)/サブタスクのテンプレート(FR-509)
3-3. テンプレートと取り込み
- SC-121 テンプレート管理(相対日付・担当・依存)(FR-707)
- プレビューで起点日を変えると日付が再計算されること(FR-708)
- SC-030 模擬チャット:「タスクにする」で自然文解析 + 確認モーダル(FR-801, FR-802)
- 元メッセージへの参照を残すこと(FR-803)
- 模擬である旨の帯を常時表示(FR-804, IMP-03)
- SC-031 模擬メール
- SC-050 仕組みの解説(3ビュー同期・自然文解析・実連携の方式と費用感)(FR-805, IMP-04)
Phase 3 完了チェック
- フローBが完走する(模擬チャット → タスク化 → 元メッセージへの参照)
- フローCが完走する(テンプレートから5タスク + 依存 → 依存の警告 → 繰り返しの自動生成)
- 4章「ロールを跨ぐ体験」の5と6が動作する
- 繰り返しを停止しても過去分が消えない
Phase 4 — チーム管理と設定(3日)
4-1. チーム
- SC-100 チームボード:担当者別の列 + 未割当の列(FR-601)
- 担当者列間のD&Dで振り替え(FR-602)
- SC-101 負荷の可視化:タスク数・期限超過・見積合計、平均線と偏りの強調(FR-603, FR-604)
- 文言を「偏りを直すため」にすること(ETH-02)
- SC-102 要対応の抽出:期限超過/停滞/未割当/ブロック中(FR-605)
- 停滞の文言を非難にしないこと(ETH-03)
- 停滞日数の設定(FR-606)
- SC-103 チームの今日(FR-610)
- SC-110 / SC-111 プロジェクト(進捗率、非公開プロジェクト)(FR-607, FR-608)
4-2. 設定・分析
- SC-130 ステータス管理:追加・削除・並び替え・色・WIP上限(FR-701, FR-702)
- 完了扱いの指定(FR-703)
- 変更がボードと全タスクに即座に反映されること(FR-704)
- 削除時に移動先を指定させること(FR-705)
- SC-131 タグ・優先度マスタ(使用件数・未使用の検出)(FR-706)
- SC-140 メンバー・権限管理(FR-709)
- SC-150 完了率・リードタイム分析。個人別ランキングを作らない(FR-710〜713, ETH-01)
- SC-151 通知設定(FR-714)/SC-041 通知センター
- SC-160 CSVエクスポート(FR-715)
Phase 4 完了チェック
- 4章「ロールを跨ぐ体験」の1・2・7・8が動作する
- ステータス列を追加すると、担当者側のボードに即座に反映される
- 個人別ランキングがどの画面にも存在しない
- 負荷の偏りが可視化され、その場で振り替えられる
Phase 5 — 記事統合・仕上げ(3日)
5-1. 記事統合
- 記事側の埋め込みコードを作成し、実際の記事ページで動作確認(EMB-01〜08)
- 埋め込みで「実際に追加できて、解析チップが出て、秒数が出る」ことを複数ブラウザで確認
- 記事の Core Web Vitals を埋め込み前後で比較検証
- 「全画面で試す」カードの設置
- 資料PDF(自作判断シート・損益分岐計算シート)の作成とダウンロード導線
- 元記事リンク・問い合わせ導線
- GA4 イベント設定。
add_secondsとparse_hitを必ず含める(EMB-09, EMB-10) - GA4 にタスクの内容を送っていないことを確認(EMB-11, SEC-03)
-
?embed=1をnoindexに、デモ本体の title / description / OGP(EMB-12)
5-2. 体験の総点検
- 4章「ロールを跨ぐ体験」の8パターンをすべて手動で確認
- 全30画面を開き、空の画面が1つもないことを確認
- タスクのタイトルにダミー文が1件も残っていないことを確認
- 空状態(データ0件/絞り込み0件)の出し分けを確認
- ETH-01〜05 の倫理要件をすべて確認(個人別ランキングがない、非難の文言がない、見積が必須でない)
- PWA / オフライン起動(F-C08)
- ガイドツアーを実装(FR-816)
5-3. アクセシビリティ監査
- axe-core を全画面に実行し、Critical / Serious を0件に
- キーボードだけでD&Dが完遂できることを確認(A11Y-03)
- カードのアクセシブルな名前が内容を要約していることを確認(A11Y-05)
- 期限・優先度・ステータスが色のみで伝えられていないことを確認(A11Y-06, A11Y-07)
- スクリーンリーダーでカードの移動と解析結果が読み上げられることを確認
- カレンダーが表としても読めることを確認(A11Y-14)
- フォントサイズ200%での確認(A11Y-15)
5-4. パフォーマンス
- グラフ・CSVを動的import に切り出し、初期バンドルを230KB以下に(NFR-14)
- ドラッグの60fps、追加100ms、ビュー切替100ms、解析16msを実測(NFR-01〜05)
- ボード300枚・カレンダー400枚の描画300ms以下を実測(NFR-07, NFR-08)
- カード100回ドラッグでのメモリ推移を計測(NFR-17)
- Lighthouse で Performance 92 / Accessibility 95(NFR-15)
5-5. 公開と記録
- Playwright で フローA・B・C の E2E(D&Dはキーボード操作で決定的に)
- Vitest:
lib/parselib/viewlib/recurlib/graph_scopeのカバレッジ85%以上 -
/dev/componentsを本番で非公開に - Vercel へデプロイ
-
add_secondsの分布とparse_hitの内訳の記録を開始(COST-01〜03) - 解説動画(3分)の収録:1行で追加 → 解析チップ → ボードにカード → カレンダーへ切替 → D&Dで期限変更 → リストで確認 → チャットからタスク化 → 繰り返しの自動生成
- 各フェーズのキャプチャを整理し、記事の「方法」パートの素材にまとめる
合計 22営業日(約4.5週間)/1名専任 + レビュー体制。
工数配分の意図: Phase 2(3ビューとD&D)に5日を割いているのは、このアプリの品質がドラッグの手触りに集約されているから。カクついた瞬間に「作りかけ」に見える。既製のカレンダー・ガントライブラリを使わないと決めた以上、ここに時間を使う。 Phase 0 に4日を割いているのは、
lib/parse/とlib/view/が全画面に波及するため。特に自然文解析は境界条件(週跨ぎ・月跨ぎ・うるう年)が多く、テストで固めないと後から追えないバグになる。
13. 受入基準
- 6章の優先度 P1 の要件がすべて実装され、テストで合格していること
- 5章の主要フローA・B・Cが、エンドツーエンドで完走すること
- 4章「ロールを跨ぐ体験」の8パターンがすべて動作すること
- 1行入力から期限・担当・タグが解析され、チップで表示されること
- チップの
×で解析を取り消し、その語がタイトルに戻ること - 解析できない入力でも必ずタスクを追加できること
- 自然文解析が16ms以下で、入力を妨げないこと
- タスクの追加が100ms以下で、その場にカードが現れること
⌘Kからどの画面でもタスクを追加でき、画面遷移しないこと- ボード・リスト・カレンダーが同一の Task を射影しており、ビューごとに別データを持っていないこと
- リストで期限を変更すると、カレンダーとボードに即座に反映されること
- カレンダーでD&Dすると、リストの期限が変わること
- ビューを切り替えても絞り込み条件が維持されること
- ビュー切替が100ms以下で、データを取り直していないこと
- どのビューからでも、期限変更・担当変更・完了ができること
- ドラッグ中のフレームレートが60fpsを維持すること
- D&Dがキーボードだけで完遂でき、移動が読み上げられること
- WIP上限を超えた列が警告表示されること
- 繰り返しタスクを完了すると次回分が自動生成され、トーストで通知されること
- 繰り返しを停止しても、過去に生成されたタスクが消えないこと
- 依存の循環が検出され、設定できないこと
- 前提が未完了のまま着手しようとすると警告が出るが、ブロックされないこと
- テンプレートから複数タスクが依存関係付きで生成され、起点日で日付が再計算されること
- 模擬チャットからタスクを生成でき、元メッセージへの参照が残ること。模擬である旨が明示されていること
- ステータス列を追加・削除すると、ボードと全タスクに即座に反映されること。削除時に移動先を指定させること
- 非公開プロジェクトのタスクが、検索を含むあらゆる経路でメンバー以外に返らないこと
- 担当者が未所属プロジェクトのタスクを取得できないこと
- 完了数・リードタイムの個人別ランキングが、どの画面にも存在しないこと
- 見積時間・実績時間の入力が必須になっていないこと
- タスクのタイトルにダミー文が1件も存在しないこと
- タスクの内容が外部に一切送信されていないこと(DevTools と GA4 のイベントで確認)
- 全30画面のいずれにも空の状態がなく、リアルなデータが表示されていること
- カードを100回ドラッグしてもメモリが単調増加しないこと
- 初期バンドルが230KB以下、Lighthouse Performance 92以上であること
- axe-core で Critical / Serious の指摘が0件であること
- データアクセスがすべて
lib/repo/を経由し、ビュー射影がlib/view/に集約されていること - 姉妹プロジェクトと並べたときに、明確に別のプロダクトとして見えること(カードの影とドラッグの手触りが効いていること)
判断に迷ったときのルール
- 仕様がこの文書にない場合は、実装せずに質問する。 推測で作らない
- 既製ツールの機能を真似しない。 自作の理由(1章・11章)に紐づかない機能は実装しない。これがこのプロジェクトで最も重要なルール
- ドラッグの手触りを最優先する。 60fpsを守れないなら実装を見直す。カクついた瞬間に全部が安っぽく見える
- 3ビューは同一データの射影。 ビューごとに別データを持たない。射影は
lib/view/に集約する - どのビューからでも同じ操作ができること。 ビューによってできることが変わらない
- 解析は「おまけ」。 賢く解析しようとして入力を妨げない。解析できなくても必ず追加できる
- AIを呼ばない。 自然文解析はルールベース。入力中のリアルタイム解析はAPIでは実現できない
- 既製のカレンダー・ガント・日付解析・全文検索ライブラリを入れない。 自前実装であることがこのデモの価値
- 非公開プロジェクトを、検索インデックスの段階で除外する。 「検索したら見えた」は致命的な事故
- 完了数・リードタイムで人を評価させない。 個人別ランキングを作らない(ETH-01)
- 負荷の可視化を「働いていない人を見つける」ために使わせない。 文言で示す(ETH-02)
- 見積時間の入力を必須にしない。 強制すると数字が嘘になる(ETH-04)
- 依存の警告でブロックしない。 現実には並行して進むこともある
- タスクのタイトルにダミー文を残さない。 タスク管理のデモはタイトルのリアリティが説得力になる
- スコープ外(3章 Won't have)は実装しない。特にAI分解・リアルタイム共同編集・工数原価は範囲外
- 「デモだから」を理由に品質を落とす判断はしない
- 姉妹プロジェクトのトークン・コンポーネントをコピーしない。 TSUMU だけがカードに影を落とす