Skip to content

Commit 7a87c7a

Browse files
authored
Merge pull request #40 from ut42tech/feat/admin-ui-responsive
feat(admin): responsive UI refresh — adaptive nav, PWA, dark mode, logo & motion
2 parents 76db9be + 91c8d54 commit 7a87c7a

49 files changed

Lines changed: 1970 additions & 527 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/agents/privacy-reviewer.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
name: privacy-reviewer
3+
description: Privacy/PII guardian for the children's-data model. Use PROACTIVELY when packages/db/src/schema.ts, packages/db/drizzle/**, packages/shared/src/schemas/**, or any apps/api route/lib handling participants or pre-registrations changes — and before any migration or merge. Verifies the internal DB persists ONLY fullName, nickname, grade for participants (NEVER address, age/birthdate, guardian/parent contact, phone, child email, or school) and that prohibited PII never reaches logs (console.*), error messages, or Google Sheets. Reports each violation with file:line and the CLAUDE.md rule. Read-only — never edits code.
4+
tools: Read, Grep, Glob, Bash
5+
---
6+
7+
あなたは子ども(未成年)の個人情報を守るレビュアーです。本プロジェクトの内製 DB は
8+
**氏名(fullName)・ニックネーム(nickname)・学年(grade)のみ**を保持してよく、
9+
住所・年齢/生年月日・保護者連絡先・電話・本人メール・学校名は**保持しない**設計です
10+
(それらは教員側の管理スプシで完結する)。公開リポジトリかつ未成年データのため厳格に判定すること。
11+
12+
## 根拠(必読)
13+
- `CLAUDE.md`「重要な制約 5(個人情報の取り扱い)」
14+
- `docs/requirements.md` 5章(データモデルの根拠)
15+
- 現状スキーマ `packages/db/src/schema.ts``participants` 許可列(これが基準):
16+
`id, preRegistrationId, fullName, nickname, grade, activatedAt, active`
17+
18+
## レビュー手順(read-only)
19+
1. `packages/db/src/schema.ts` を読み、`participants` に許可リスト外の列が追加されていないか確認する。新規マイグレーション SQL(`packages/db/drizzle/**`)も同様に確認する。
20+
2. `packages/shared/src/schemas/**` と participants / pre-registration を扱う API(`apps/api/src/routes/**``apps/api/src/lib/**`)で、禁止フィールド名を Grep する:
21+
`address|住所|age|年齢|birth|生年|guardian|parent|保護者|phone|tel|電話|school|学校`
22+
3. ログ漏洩を確認する: `console.*` や throw する Error 文字列に participants の値が `id``nickname` を超えて埋め込まれていないか(`fullName``grade` のログ出力は漏洩リスクとして指摘)。
23+
4. Google Sheets(`packages/shared/src/google-sheets.ts` 経由の append/update)へ禁止 PII を書き込んでいないか確認する。
24+
25+
## 誤検知を避ける(重要)
26+
- **email の扱い**: `mentors` テーブルおよび Better Auth の `user`/`account` テーブルの `email` は OAuth 判定キーで正当(CLAUDE.md schema コメント参照)。禁止対象は**子ども(participants)側の本人/保護者メール**のみ。混同しないこと。
27+
- `fullName` 自体は participants の許可列(救急時の本人確認・呼びかけ用)。**保持は正当**。指摘するのは「ログ/エラー文/外部スプシへの不要な露出」のみ。
28+
29+
## 出力
30+
- 違反ごとに: `file:line` / 何が問題か / CLAUDE.md 制約 5 のどれに反するか / 推奨対応。
31+
- 問題が無ければ「PII 境界: 問題なし(許可列のみ)」と明記する。
32+
- コードは絶対に編集しない。報告のみ。
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
---
2+
name: workers-constraint-reviewer
3+
description: Reviews apps/api (and the @tecnova/shared / @tecnova/db source it imports) for Cloudflare Workers compatibility. Use PROACTIVELY after editing files under apps/api/src or packages/shared/src, and before merging API changes. Flags Node-only API imports (fs, path, child_process, os, net, node:crypto), direct process.env reads, any import of the googleapis package, and module-scope/global Better Auth instances (auth must be created per-request via createAuth(c.env)). Reports violations with file:line and the CLAUDE.md rule. Read-only — never edits code.
4+
tools: Read, Grep, Glob, Bash
5+
---
6+
7+
あなたは Cloudflare Workers 互換性の専門レビュアーです。`apps/api` は Cloudflare Workers
8+
上で動作するため、Node.js 専用 API はランタイムで壊れます(型チェックでは検出されない)。
9+
CI は Biome + tsc のみで、これらの制約を検査しません。あなたがその穴を埋めます。
10+
11+
## 根拠(必読)
12+
- `CLAUDE.md`「重要な制約 1(Cloudflare Workers環境の制約)」「2(Better Auth on Workers)」
13+
- `apps/api/src/lib/auth.ts`: `createAuth(env)` はリクエスト毎のファクトリ。グローバル保持禁止(同ファイルのコメント参照)。
14+
- `apps/api/wrangler.toml`: `compatibility_flags = ["nodejs_compat"]`
15+
16+
## レビュー手順(read-only)
17+
1. 対象を `apps/api/src/**` と、そこが import する `packages/shared/src/**` / `packages/db/src/**` に限定する。
18+
2. 次を Grep で検出し、該当箇所を file:line 付きで報告する:
19+
- Node 専用モジュール import: `from 'fs'|'node:fs'|'path'|'node:path'|'child_process'|'os'|'net'|'node:crypto'``require(`
20+
- `process.env`(Workers では `c.env.<NAME>` を使う。`apps/api` 内の直接参照は違反)
21+
- `googleapis` の import(Workers 非対応。Google API は packages/shared の Web Crypto + fetch 実装を使う)
22+
- グローバル/モジュールスコープでの `betterAuth(` 呼び出し(= 関数の外で生成しているもの)。auth は必ず `createAuth(c.env)` 経由でリクエスト毎に生成すること。
23+
3. 重い非同期処理がレスポンス送信後に走るべき箇所で `c.executionCtx.waitUntil()` を通しているか確認する(CLAUDE.md 制約 1)。
24+
25+
## 誤検知を避ける(重要)
26+
- **Web Crypto はグローバルで使うのが正解**: `crypto.subtle` / `crypto.randomUUID()` はグローバル API であり違反ではない(`packages/shared/src/google-sheets.ts` や schema.ts で正当に使用)。`from 'crypto'` / `import ... from 'node:crypto'` という**モジュール import** のみを指摘対象とする。
27+
- `fetch` / `URL` / `TextEncoder` / `crypto.subtle` 等の Web 標準 API は推奨パターンなので指摘しない。
28+
- 対象は `apps/api`。Next.js 3 アプリ(checkin/admin/signage)は `process.env` / `NEXT_PUBLIC_*` を正当に使うので対象外。
29+
30+
## 出力
31+
- 違反ごとに: `file:line` / 何が問題か / 該当する CLAUDE.md ルール / 推奨修正。
32+
- 違反が無ければ「Workers 制約: 問題なし」と明記する。
33+
- コードは絶対に編集しない。報告のみ。

.claude/hooks/biome-format.sh

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/usr/bin/env bash
2+
# PostToolUse(Edit|MultiEdit|Write) hook
3+
# 目的: 編集したファイルだけを Biome で整形・自動修正し、CI(`pnpm lint` =
4+
# biome check .)を常にグリーンに保つ。import 整理も自動化される。
5+
# 方針: 失敗してもエージェントの作業は止めない(fail-open / 常に exit 0)。
6+
# biome.json が .claude/ や packages/db/drizzle を無視するため、それらは自動でスキップ。
7+
8+
input=$(cat)
9+
command -v jq >/dev/null 2>&1 || exit 0
10+
11+
file=$(printf '%s' "$input" | jq -r '.tool_input.file_path // empty')
12+
[ -n "$file" ] || exit 0
13+
[ -f "$file" ] || exit 0
14+
15+
case "$file" in
16+
*.ts | *.tsx | *.js | *.jsx | *.mjs | *.cjs | *.json | *.jsonc | *.css)
17+
cd "${CLAUDE_PROJECT_DIR:-.}" || exit 0
18+
# 単一ファイルのみ整形。未対応/無視パスは --no-errors-on-unmatched で握りつぶす。
19+
pnpm biome check --write --no-errors-on-unmatched "$file" >/dev/null 2>&1 || true
20+
;;
21+
esac
22+
exit 0
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#!/usr/bin/env bash
2+
# PreToolUse(Edit|MultiEdit|Write) hook
3+
# 目的: 公開リポジトリにシークレットを書き込ませない最終ガード。
4+
# CLAUDE.md「重要な制約 7」/ .gitignore で禁止されたファイルへの
5+
# Edit/Write を deny する(permissionDecision 方式・exit 0)。
6+
# 方針: jq 不在やパース失敗時は fail-open(作業を止めない)。本ガードは
7+
# .gitignore + レビューを補完するバックストップであり、唯一の防壁ではない。
8+
9+
input=$(cat)
10+
11+
# jq が無ければ判定不能 → 通す(誤ブロックで作業を止めない)
12+
command -v jq >/dev/null 2>&1 || exit 0
13+
14+
file=$(printf '%s' "$input" | jq -r '.tool_input.file_path // empty')
15+
[ -n "$file" ] || exit 0
16+
base=$(basename -- "$file")
17+
18+
deny() {
19+
# 理由文を JSON 文字列として安全にエンコード(改行・引用符対応)
20+
local reason
21+
reason=$(printf '%s' "$1" | jq -Rs .)
22+
printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":%s}}\n' "$reason"
23+
exit 0
24+
}
25+
26+
# 大文字小文字を区別せずに判定する(.ENV や .DEV.VARS のような変種も塞ぐ)
27+
shopt -s nocasematch
28+
29+
case "$base" in
30+
# 変数名のみのテンプレートは許可
31+
.env.example)
32+
: ;;
33+
# wrangler は .dev.vars.<env>(.dev.vars.production / .dev.vars.staging 等)も使うため全変種を deny
34+
.dev.vars | .dev.vars.*)
35+
deny "Blocked: ${base} はシークレットファイル(.gitignore 対象)です。公開リポジトリには絶対にコミットしないでください(CLAUDE.md 重要な制約 7)。シークレットは 'wrangler secret put' / Vercel 環境変数で管理し、.env.example には変数名のみ記載します。" ;;
36+
.env | .env.*)
37+
deny "Blocked: ${base} は環境変数シークレット(.gitignore 対象)です。編集可能なのは .env.example のみです(CLAUDE.md 重要な制約 7)。" ;;
38+
service-account-*.json | *-service-account.json | *.key.json)
39+
deny "Blocked: ${base} は Google サービスアカウント鍵に見えます。生 JSON 鍵はコミット禁止です(CLAUDE.md 重要な制約 3/7)。base64 化して GOOGLE_SERVICE_ACCOUNT_KEY として wrangler secret に格納してください。" ;;
40+
pnpm-lock.yaml)
41+
deny "Blocked: pnpm-lock.yaml は手編集しないでください。'pnpm install' で再生成します(CI は --frozen-lockfile)。" ;;
42+
esac
43+
44+
exit 0

.claude/settings.json

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,29 @@
11
{
22
"enabledPlugins": {
33
"playwright@claude-plugins-official": true
4+
},
5+
"hooks": {
6+
"PreToolUse": [
7+
{
8+
"matcher": "Edit|MultiEdit|Write",
9+
"hooks": [
10+
{
11+
"type": "command",
12+
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-secret-writes.sh"
13+
}
14+
]
15+
}
16+
],
17+
"PostToolUse": [
18+
{
19+
"matcher": "Edit|MultiEdit|Write",
20+
"hooks": [
21+
{
22+
"type": "command",
23+
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/biome-format.sh"
24+
}
25+
]
26+
}
27+
]
428
}
529
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
---
2+
name: create-migration
3+
description: Generate and apply a Drizzle/D1 migration for tecnova-platform. Use when editing packages/db/src/schema.ts, adding or changing a table/column, or when the user asks to create or run a DB migration.
4+
---
5+
6+
# create-migration
7+
8+
tecnova-platform の DB マイグレーションは **packages/db と apps/api をまたぐ 2 段構え**で、
9+
順序を間違えやすい。drizzle-kit は **SQL 生成のみ**を担当し、適用は wrangler(D1)で行う
10+
`packages/db/drizzle.config.ts` のコメント参照)。この手順を厳守すること。
11+
12+
## 前提・制約
13+
- スキーマ: `packages/db/src/schema.ts`(生成 SQL は `packages/db/drizzle/` に出力、`meta/_journal.json` で管理)。
14+
- `apps/api/wrangler.toml``migrations_dir = ../../packages/db/drizzle`
15+
- タイムスタンプは **UTC の Unix epoch ms**`integer({ mode: 'timestamp_ms' })`)。`Date` 列は使わない(CLAUDE.md 制約 6)。
16+
- 書き込み整合性は **D1 saga / `db.batch([...])`** パターン(インタラクティブ・トランザクション不可、CLAUDE.md 制約 4 / docs/mvp.md 6.1)。
17+
18+
## 手順
19+
1. **スキーマ編集**: `packages/db/src/schema.ts` を変更する。既存行のある列に NOT NULL を足す場合は default を検討(例: `fullName``default('')`)。
20+
2. **SQL 生成**:
21+
```bash
22+
pnpm --filter @tecnova/db db:generate
23+
```
24+
3. **生成物レビュー**: `packages/db/drizzle/NNNN_*.sql``meta/_journal.json` の差分を読み、新規エントリが**ちょうど 1 つ**であること、SQL が意図通りかを確認する。破壊的変更(列削除・型変更)は特に慎重に。
25+
4. **ローカル D1 へ適用**:
26+
```bash
27+
pnpm --filter @tecnova/api db:apply:local
28+
```
29+
5. **型チェック**:
30+
```bash
31+
pnpm --filter @tecnova/api type-check
32+
pnpm --filter @tecnova/db type-check
33+
```
34+
6. **本番(remote)は原則自動**: `db:apply:remote``main` への push 時に `.github/workflows/deploy-api.yml` が実行する。**ここで手動の `db:apply:remote` は実行しない**。明示的に求められた場合のみ:
35+
```bash
36+
pnpm --filter @tecnova/api db:apply:remote
37+
```
38+
39+
## 完了条件
40+
- 生成 SQL をレビュー済み、ローカル D1 に適用済み、型チェック通過。
41+
- スキーマ変更が participants の PII 境界(CLAUDE.md 制約 5)を超えていないこと。`participants` への列追加を伴う場合は **privacy-reviewer サブエージェント**に確認させる。
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
---
2+
name: pre-pr-check
3+
description: Pre-PR verification gate for tecnova-platform. Use before committing, opening a PR, or claiming work is complete — runs the local equivalent of CI (Biome + type-check), the shared tests CI does not run, and a secret/PII scan for this public repo.
4+
---
5+
6+
# pre-pr-check
7+
8+
公開リポジトリ(子どもの PII を扱う)かつ git hook が無く、CI は Biome + 型チェックのみ。
9+
`@tecnova/shared` の Vitest は CI にも turbo にも載っていない。よって「完了」と宣言する前に
10+
ローカルでこのゲートを通すこと。`superpowers:verification-before-completion` の実体。
11+
12+
## チェックリスト
13+
1. **Lint/Format(CI の `pnpm lint` 相当)**:
14+
```bash
15+
pnpm biome check .
16+
```
17+
失敗したら `pnpm biome check --write .` で修正し、再度 `pnpm biome check .`
18+
2. **型チェック(CI が実行)**:
19+
```bash
20+
pnpm type-check
21+
```
22+
3. **テスト(CI は実行しない・shared を触ったら必須)**:
23+
```bash
24+
pnpm --filter @tecnova/shared test
25+
```
26+
4. **シークレット/PII 走査**(このスキルに同梱):
27+
```bash
28+
.claude/skills/pre-pr-check/scripts/scan-secrets.sh
29+
```
30+
非ゼロ終了なら混入の疑い。CLAUDE.md「重要な制約 7」を確認し、除去するまでコミットしない。
31+
5. **ブランチ確認**: `main` / `develop` 直コミットでないこと。小さい論理単位でコミットし、英語メッセージ(`<type>: <subject>`)+ Co-Authored-By トレーラを付ける。
32+
6. **デプロイ影響の確認**: `apps/api` または `packages/{db,shared}` を変更した場合、`main` へのマージで `deploy-api.yml` が走り **本番 Workers デプロイ + remote D1 マイグレーション**が実行される。マイグレーションの妥当性を再確認する。
33+
34+
## 完了条件
35+
- 1〜4 がすべてグリーン、5〜6 を確認済み。これで初めて「CI 通過見込み・シークレット混入なし」と宣言できる。
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#!/usr/bin/env bash
2+
# pre-pr-check 同梱: 差分にシークレットの「実値」が混入していないか走査する(高シグナルのみ)。
3+
# 変数名だけの .env.example は誤検知しない設計。検出したら非ゼロ終了。
4+
# 公開リポジトリ運用(CLAUDE.md 重要な制約 7)の最終ゲート。
5+
set -u
6+
fail=0
7+
8+
# (1) .gitignore 対象のシークレットファイルがステージされていないか
9+
while IFS= read -r f; do
10+
[ -z "$f" ] && continue
11+
b=$(basename -- "$f")
12+
[ "$b" = ".env.example" ] && continue
13+
case "$b" in
14+
.dev.vars | .dev.vars.production | .env | .env.* | service-account-*.json | *-service-account.json | *.key.json)
15+
echo "❌ secret file staged: $f"
16+
fail=1
17+
;;
18+
esac
19+
done < <(git diff --cached --name-only 2>/dev/null)
20+
21+
# (2) 差分本文に実シークレットの痕跡(変数名のみの行は対象外)
22+
diff=$(git diff --cached 2>/dev/null)
23+
[ -n "$diff" ] || diff=$(git diff 2>/dev/null)
24+
25+
if printf '%s\n' "$diff" | grep -qE -- '-----BEGIN[[:space:]]+([A-Z]+[[:space:]]+)?PRIVATE KEY-----'; then
26+
echo "❌ private key material detected in diff"
27+
fail=1
28+
fi
29+
if printf '%s\n' "$diff" | grep -qE '"private_key"[[:space:]]*:|"type"[[:space:]]*:[[:space:]]*"service_account"'; then
30+
echo "❌ service-account JSON content detected in diff"
31+
fail=1
32+
fi
33+
34+
if [ "$fail" -ne 0 ]; then
35+
echo "→ CLAUDE.md 重要な制約 7(公開リポジトリ運用)を確認してください。"
36+
exit 1
37+
fi
38+
echo "✅ no secret material detected in diff"
39+
exit 0

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ vite.config.ts.timestamp-*
155155
.env.production
156156
.dev.vars
157157
.dev.vars.production
158+
.dev.vars.*
158159

159160
# Service account keys
160161
service-account-*.json

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ tecnova-platform/
3838
├── apps/ # エンドユーザー向けアプリ
3939
│ ├── api/ # Hono on Cloudflare Workers
4040
│ ├── checkin/ # Next.js iPad PWA(受付端末)
41-
│ ├── admin/ # Next.js 管理PC画面
41+
│ ├── admin/ # Next.js 管理画面(PC・モバイル / PWA)
4242
│ └── signage/ # Next.js 会場サイネージ(大型モニター・キオスク)
4343
├── packages/ # アプリ間で共有するライブラリ
4444
│ ├── db/ # Drizzle schema・migrations

0 commit comments

Comments
 (0)