Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
62b96c9
docs: add admin UI refresh design spec
ut42tech Jun 2, 2026
7546adf
feat(ui): add theme provider/toggle and make Toaster theme-aware
ut42tech Jun 2, 2026
1d6a400
feat(admin): responsive nav shell (sidebar + bottom tabs) and theme w…
ut42tech Jun 2, 2026
67f50a0
feat(admin): make installable as a PWA on mobile
ut42tech Jun 2, 2026
62ec029
feat(admin): responsive per-page layouts (tables become cards on mobile)
ut42tech Jun 2, 2026
916dccc
fix(admin): drop invalid manifest favicon icon, declutter mobile KPIs
ut42tech Jun 2, 2026
d98a75a
chore(claude): add secret-guard and Biome-format hooks
ut42tech Jun 2, 2026
d5bda59
chore(claude): add workers-constraint and privacy reviewer subagents
ut42tech Jun 2, 2026
4157070
chore(claude): add create-migration and pre-pr-check skills
ut42tech Jun 2, 2026
312d3a3
fix(admin): address review findings (a11y + responsive)
ut42tech Jun 2, 2026
c9ff478
docs: note admin responsive/dark-mode/PWA in CLAUDE.md
ut42tech Jun 2, 2026
16fc708
feat(admin): adopt official tec-nova logo for brand marks and PWA icons
ut42tech Jun 2, 2026
c974cb1
feat(admin): add restrained motion micro-interactions and refine nav
ut42tech Jun 2, 2026
b49f9a6
docs(admin): document logo, motion, and PWA icon conventions
ut42tech Jun 2, 2026
254cf49
fix(admin): match sidebar active-pill radius to the button shape
ut42tech Jun 2, 2026
89a14b5
fix(ui): restore open/close animation on dropdown menu and select
ut42tech Jun 2, 2026
91c8d54
fix(admin): shape the participant detail loading skeleton like its co…
ut42tech Jun 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .claude/agents/privacy-reviewer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
name: privacy-reviewer
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.
tools: Read, Grep, Glob, Bash
---

あなたは子ども(未成年)の個人情報を守るレビュアーです。本プロジェクトの内製 DB は
**氏名(fullName)・ニックネーム(nickname)・学年(grade)のみ**を保持してよく、
住所・年齢/生年月日・保護者連絡先・電話・本人メール・学校名は**保持しない**設計です
(それらは教員側の管理スプシで完結する)。公開リポジトリかつ未成年データのため厳格に判定すること。

## 根拠(必読)
- `CLAUDE.md`「重要な制約 5(個人情報の取り扱い)」
- `docs/requirements.md` 5章(データモデルの根拠)
- 現状スキーマ `packages/db/src/schema.ts` の `participants` 許可列(これが基準):
`id, preRegistrationId, fullName, nickname, grade, activatedAt, active`

## レビュー手順(read-only)
1. `packages/db/src/schema.ts` を読み、`participants` に許可リスト外の列が追加されていないか確認する。新規マイグレーション SQL(`packages/db/drizzle/**`)も同様に確認する。
2. `packages/shared/src/schemas/**` と participants / pre-registration を扱う API(`apps/api/src/routes/**`・`apps/api/src/lib/**`)で、禁止フィールド名を Grep する:
`address|住所|age|年齢|birth|生年|guardian|parent|保護者|phone|tel|電話|school|学校`
3. ログ漏洩を確認する: `console.*` や throw する Error 文字列に participants の値が `id`・`nickname` を超えて埋め込まれていないか(`fullName`・`grade` のログ出力は漏洩リスクとして指摘)。
4. Google Sheets(`packages/shared/src/google-sheets.ts` 経由の append/update)へ禁止 PII を書き込んでいないか確認する。

## 誤検知を避ける(重要)
- **email の扱い**: `mentors` テーブルおよび Better Auth の `user`/`account` テーブルの `email` は OAuth 判定キーで正当(CLAUDE.md schema コメント参照)。禁止対象は**子ども(participants)側の本人/保護者メール**のみ。混同しないこと。
- `fullName` 自体は participants の許可列(救急時の本人確認・呼びかけ用)。**保持は正当**。指摘するのは「ログ/エラー文/外部スプシへの不要な露出」のみ。

## 出力
- 違反ごとに: `file:line` / 何が問題か / CLAUDE.md 制約 5 のどれに反するか / 推奨対応。
- 問題が無ければ「PII 境界: 問題なし(許可列のみ)」と明記する。
- コードは絶対に編集しない。報告のみ。
33 changes: 33 additions & 0 deletions .claude/agents/workers-constraint-reviewer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
name: workers-constraint-reviewer
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.
tools: Read, Grep, Glob, Bash
---

あなたは Cloudflare Workers 互換性の専門レビュアーです。`apps/api` は Cloudflare Workers
上で動作するため、Node.js 専用 API はランタイムで壊れます(型チェックでは検出されない)。
CI は Biome + tsc のみで、これらの制約を検査しません。あなたがその穴を埋めます。

## 根拠(必読)
- `CLAUDE.md`「重要な制約 1(Cloudflare Workers環境の制約)」「2(Better Auth on Workers)」
- `apps/api/src/lib/auth.ts`: `createAuth(env)` はリクエスト毎のファクトリ。グローバル保持禁止(同ファイルのコメント参照)。
- `apps/api/wrangler.toml`: `compatibility_flags = ["nodejs_compat"]`。

## レビュー手順(read-only)
1. 対象を `apps/api/src/**` と、そこが import する `packages/shared/src/**` / `packages/db/src/**` に限定する。
2. 次を Grep で検出し、該当箇所を file:line 付きで報告する:
- Node 専用モジュール import: `from 'fs'|'node:fs'|'path'|'node:path'|'child_process'|'os'|'net'|'node:crypto'`、`require(`
- `process.env`(Workers では `c.env.<NAME>` を使う。`apps/api` 内の直接参照は違反)
- `googleapis` の import(Workers 非対応。Google API は packages/shared の Web Crypto + fetch 実装を使う)
- グローバル/モジュールスコープでの `betterAuth(` 呼び出し(= 関数の外で生成しているもの)。auth は必ず `createAuth(c.env)` 経由でリクエスト毎に生成すること。
3. 重い非同期処理がレスポンス送信後に走るべき箇所で `c.executionCtx.waitUntil()` を通しているか確認する(CLAUDE.md 制約 1)。

## 誤検知を避ける(重要)
- **Web Crypto はグローバルで使うのが正解**: `crypto.subtle` / `crypto.randomUUID()` はグローバル API であり違反ではない(`packages/shared/src/google-sheets.ts` や schema.ts で正当に使用)。`from 'crypto'` / `import ... from 'node:crypto'` という**モジュール import** のみを指摘対象とする。
- `fetch` / `URL` / `TextEncoder` / `crypto.subtle` 等の Web 標準 API は推奨パターンなので指摘しない。
- 対象は `apps/api`。Next.js 3 アプリ(checkin/admin/signage)は `process.env` / `NEXT_PUBLIC_*` を正当に使うので対象外。

## 出力
- 違反ごとに: `file:line` / 何が問題か / 該当する CLAUDE.md ルール / 推奨修正。
- 違反が無ければ「Workers 制約: 問題なし」と明記する。
- コードは絶対に編集しない。報告のみ。
22 changes: 22 additions & 0 deletions .claude/hooks/biome-format.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env bash
# PostToolUse(Edit|MultiEdit|Write) hook
# 目的: 編集したファイルだけを Biome で整形・自動修正し、CI(`pnpm lint` =
# biome check .)を常にグリーンに保つ。import 整理も自動化される。
# 方針: 失敗してもエージェントの作業は止めない(fail-open / 常に exit 0)。
# biome.json が .claude/ や packages/db/drizzle を無視するため、それらは自動でスキップ。

input=$(cat)
command -v jq >/dev/null 2>&1 || exit 0

file=$(printf '%s' "$input" | jq -r '.tool_input.file_path // empty')
[ -n "$file" ] || exit 0
[ -f "$file" ] || exit 0

case "$file" in
*.ts | *.tsx | *.js | *.jsx | *.mjs | *.cjs | *.json | *.jsonc | *.css)
cd "${CLAUDE_PROJECT_DIR:-.}" || exit 0
# 単一ファイルのみ整形。未対応/無視パスは --no-errors-on-unmatched で握りつぶす。
pnpm biome check --write --no-errors-on-unmatched "$file" >/dev/null 2>&1 || true
;;
esac
exit 0
44 changes: 44 additions & 0 deletions .claude/hooks/block-secret-writes.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/bin/env bash
# PreToolUse(Edit|MultiEdit|Write) hook
# 目的: 公開リポジトリにシークレットを書き込ませない最終ガード。
# CLAUDE.md「重要な制約 7」/ .gitignore で禁止されたファイルへの
# Edit/Write を deny する(permissionDecision 方式・exit 0)。
# 方針: jq 不在やパース失敗時は fail-open(作業を止めない)。本ガードは
# .gitignore + レビューを補完するバックストップであり、唯一の防壁ではない。

input=$(cat)

# jq が無ければ判定不能 → 通す(誤ブロックで作業を止めない)
command -v jq >/dev/null 2>&1 || exit 0

file=$(printf '%s' "$input" | jq -r '.tool_input.file_path // empty')
[ -n "$file" ] || exit 0
base=$(basename -- "$file")

deny() {
# 理由文を JSON 文字列として安全にエンコード(改行・引用符対応)
local reason
reason=$(printf '%s' "$1" | jq -Rs .)
printf '{"hookSpecificOutput":{"hookEventName":"PreToolUse","permissionDecision":"deny","permissionDecisionReason":%s}}\n' "$reason"
exit 0
}

# 大文字小文字を区別せずに判定する(.ENV や .DEV.VARS のような変種も塞ぐ)
shopt -s nocasematch

case "$base" in
# 変数名のみのテンプレートは許可
.env.example)
: ;;
# wrangler は .dev.vars.<env>(.dev.vars.production / .dev.vars.staging 等)も使うため全変種を deny
.dev.vars | .dev.vars.*)
deny "Blocked: ${base} はシークレットファイル(.gitignore 対象)です。公開リポジトリには絶対にコミットしないでください(CLAUDE.md 重要な制約 7)。シークレットは 'wrangler secret put' / Vercel 環境変数で管理し、.env.example には変数名のみ記載します。" ;;
.env | .env.*)
deny "Blocked: ${base} は環境変数シークレット(.gitignore 対象)です。編集可能なのは .env.example のみです(CLAUDE.md 重要な制約 7)。" ;;
service-account-*.json | *-service-account.json | *.key.json)
deny "Blocked: ${base} は Google サービスアカウント鍵に見えます。生 JSON 鍵はコミット禁止です(CLAUDE.md 重要な制約 3/7)。base64 化して GOOGLE_SERVICE_ACCOUNT_KEY として wrangler secret に格納してください。" ;;
pnpm-lock.yaml)
deny "Blocked: pnpm-lock.yaml は手編集しないでください。'pnpm install' で再生成します(CI は --frozen-lockfile)。" ;;
esac

exit 0
24 changes: 24 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,29 @@
{
"enabledPlugins": {
"playwright@claude-plugins-official": true
},
"hooks": {
"PreToolUse": [
{
"matcher": "Edit|MultiEdit|Write",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-secret-writes.sh"
}
]
}
],
"PostToolUse": [
{
"matcher": "Edit|MultiEdit|Write",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/biome-format.sh"
}
]
}
]
}
}
41 changes: 41 additions & 0 deletions .claude/skills/create-migration/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
---
name: create-migration
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.
---

# create-migration

tecnova-platform の DB マイグレーションは **packages/db と apps/api をまたぐ 2 段構え**で、
順序を間違えやすい。drizzle-kit は **SQL 生成のみ**を担当し、適用は wrangler(D1)で行う
(`packages/db/drizzle.config.ts` のコメント参照)。この手順を厳守すること。

## 前提・制約
- スキーマ: `packages/db/src/schema.ts`(生成 SQL は `packages/db/drizzle/` に出力、`meta/_journal.json` で管理)。
- `apps/api/wrangler.toml` の `migrations_dir = ../../packages/db/drizzle`。
- タイムスタンプは **UTC の Unix epoch ms**(`integer({ mode: 'timestamp_ms' })`)。`Date` 列は使わない(CLAUDE.md 制約 6)。
- 書き込み整合性は **D1 saga / `db.batch([...])`** パターン(インタラクティブ・トランザクション不可、CLAUDE.md 制約 4 / docs/mvp.md 6.1)。

## 手順
1. **スキーマ編集**: `packages/db/src/schema.ts` を変更する。既存行のある列に NOT NULL を足す場合は default を検討(例: `fullName` は `default('')`)。
2. **SQL 生成**:
```bash
pnpm --filter @tecnova/db db:generate
```
3. **生成物レビュー**: `packages/db/drizzle/NNNN_*.sql` と `meta/_journal.json` の差分を読み、新規エントリが**ちょうど 1 つ**であること、SQL が意図通りかを確認する。破壊的変更(列削除・型変更)は特に慎重に。
4. **ローカル D1 へ適用**:
```bash
pnpm --filter @tecnova/api db:apply:local
```
5. **型チェック**:
```bash
pnpm --filter @tecnova/api type-check
pnpm --filter @tecnova/db type-check
```
6. **本番(remote)は原則自動**: `db:apply:remote` は `main` への push 時に `.github/workflows/deploy-api.yml` が実行する。**ここで手動の `db:apply:remote` は実行しない**。明示的に求められた場合のみ:
```bash
pnpm --filter @tecnova/api db:apply:remote
```

## 完了条件
- 生成 SQL をレビュー済み、ローカル D1 に適用済み、型チェック通過。
- スキーマ変更が participants の PII 境界(CLAUDE.md 制約 5)を超えていないこと。`participants` への列追加を伴う場合は **privacy-reviewer サブエージェント**に確認させる。
35 changes: 35 additions & 0 deletions .claude/skills/pre-pr-check/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
name: pre-pr-check
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.
---

# pre-pr-check

公開リポジトリ(子どもの PII を扱う)かつ git hook が無く、CI は Biome + 型チェックのみ。
`@tecnova/shared` の Vitest は CI にも turbo にも載っていない。よって「完了」と宣言する前に
ローカルでこのゲートを通すこと。`superpowers:verification-before-completion` の実体。

## チェックリスト
1. **Lint/Format(CI の `pnpm lint` 相当)**:
```bash
pnpm biome check .
```
失敗したら `pnpm biome check --write .` で修正し、再度 `pnpm biome check .`。
2. **型チェック(CI が実行)**:
```bash
pnpm type-check
```
3. **テスト(CI は実行しない・shared を触ったら必須)**:
```bash
pnpm --filter @tecnova/shared test
```
4. **シークレット/PII 走査**(このスキルに同梱):
```bash
.claude/skills/pre-pr-check/scripts/scan-secrets.sh
```
非ゼロ終了なら混入の疑い。CLAUDE.md「重要な制約 7」を確認し、除去するまでコミットしない。
5. **ブランチ確認**: `main` / `develop` 直コミットでないこと。小さい論理単位でコミットし、英語メッセージ(`<type>: <subject>`)+ Co-Authored-By トレーラを付ける。
6. **デプロイ影響の確認**: `apps/api` または `packages/{db,shared}` を変更した場合、`main` へのマージで `deploy-api.yml` が走り **本番 Workers デプロイ + remote D1 マイグレーション**が実行される。マイグレーションの妥当性を再確認する。

## 完了条件
- 1〜4 がすべてグリーン、5〜6 を確認済み。これで初めて「CI 通過見込み・シークレット混入なし」と宣言できる。
39 changes: 39 additions & 0 deletions .claude/skills/pre-pr-check/scripts/scan-secrets.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/usr/bin/env bash
# pre-pr-check 同梱: 差分にシークレットの「実値」が混入していないか走査する(高シグナルのみ)。
# 変数名だけの .env.example は誤検知しない設計。検出したら非ゼロ終了。
# 公開リポジトリ運用(CLAUDE.md 重要な制約 7)の最終ゲート。
set -u
fail=0

# (1) .gitignore 対象のシークレットファイルがステージされていないか
while IFS= read -r f; do
[ -z "$f" ] && continue
b=$(basename -- "$f")
[ "$b" = ".env.example" ] && continue
case "$b" in
.dev.vars | .dev.vars.production | .env | .env.* | service-account-*.json | *-service-account.json | *.key.json)
echo "❌ secret file staged: $f"
fail=1
;;
esac
done < <(git diff --cached --name-only 2>/dev/null)

# (2) 差分本文に実シークレットの痕跡(変数名のみの行は対象外)
diff=$(git diff --cached 2>/dev/null)
[ -n "$diff" ] || diff=$(git diff 2>/dev/null)

if printf '%s\n' "$diff" | grep -qE -- '-----BEGIN[[:space:]]+([A-Z]+[[:space:]]+)?PRIVATE KEY-----'; then
echo "❌ private key material detected in diff"
fail=1
fi
if printf '%s\n' "$diff" | grep -qE '"private_key"[[:space:]]*:|"type"[[:space:]]*:[[:space:]]*"service_account"'; then
echo "❌ service-account JSON content detected in diff"
fail=1
fi

if [ "$fail" -ne 0 ]; then
echo "→ CLAUDE.md 重要な制約 7(公開リポジトリ運用)を確認してください。"
exit 1
fi
echo "✅ no secret material detected in diff"
exit 0
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ vite.config.ts.timestamp-*
.env.production
.dev.vars
.dev.vars.production
.dev.vars.*

# Service account keys
service-account-*.json
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ tecnova-platform/
├── apps/ # エンドユーザー向けアプリ
│ ├── api/ # Hono on Cloudflare Workers
│ ├── checkin/ # Next.js iPad PWA(受付端末)
│ ├── admin/ # Next.js 管理PC画面
│ ├── admin/ # Next.js 管理画面(PC・モバイル / PWA)
│ └── signage/ # Next.js 会場サイネージ(大型モニター・キオスク)
├── packages/ # アプリ間で共有するライブラリ
│ ├── db/ # Drizzle schema・migrations
Expand Down
Loading
Loading