Skip to content

feat(classroom): クラス管理右上のお知らせ通知センター (#1111)#1113

Merged
smalruby3-editor-bot[bot] merged 6 commits into
topic/classroom-notification-centerfrom
feature/notification-center-1111
Jul 25, 2026
Merged

feat(classroom): クラス管理右上のお知らせ通知センター (#1111)#1113
smalruby3-editor-bot[bot] merged 6 commits into
topic/classroom-notification-centerfrom
feature/notification-center-1111

Conversation

@smalruby3-editor-bot

Copy link
Copy Markdown

Summary

EPIC #1111 の実装。クラス管理画面の右上に「お知らせ」通知センター(🔔 + 未読バッジ + 一覧パネル)を追加し、Admin からお知らせを送る最小の仕組みを揃える。後続の #1110(推薦通知)/ #1106(共有促し)がこの基盤に乗る。

アーキテクチャ(設計判断)

  • classroom スタックが ClassroomNotifications{suffix} テーブルを所有(PK teacherSub / SK notificationId = createdAt 接頭辞、TTL 180日)。先生の読み出しは既存の先生 ID Token 認証をそのまま利用
  • admin スタックが単一の書き手: 名前規約 import + write-only grant(N2 維持・SharedAssignments と同型)。エディタ側からお知らせを偽造できない
  • 宛先の teacherSub は SPA に出さない: admin は classroomId を送り、サーバー側で所有者を解決(authorSub を隠す既存原則の踏襲)
  • link.kind は whitelist(現在 classroom のみ)。エディタは未知の kind を無視(前方互換)

各コミットのメッセージに、その時点の設計分岐(採用案 / 代替案 / 理由)を記録済み。分岐をやり直す場合は該当コミットに立ち戻れる。

Changes Made

  • infra/smalruby-classroom: ClassroomNotifications テーブル + GET /notifications(一覧+未読数)/ POST /notifications/mark-read(ids 省略で全既読)
  • infra/smalruby-admin: POST /admin/notifications(audit 付き・write-only grant)
  • packages/admin: クラス詳細に「先生へのお知らせ」送信フォーム(二段階確認)
  • packages/scratch-gui: タイトルバー 🔔 + 未読バッジ + 一覧パネル(開いたら全既読)+ link.kind='classroom' で該当クラスへジャンプ + 60 秒ポーリング
  • docs: architecture / source-code / ui-ux(data-testid 一覧)/ admin README + スクリーンショット 2 枚
  • tools/playwright-verify: verify-notification-center.mjs(API スタブ式・stg 不要)

Test Coverage

  • classroom infra: handler-notifications.test.ts 8 件(全 13 スイート 227 件パス)
  • admin infra: handler-notifications.test.ts 5 件(全 4 スイート 44 件パス)
  • admin SPA: classrooms-view テスト +1 件(lint + 38 件パス)
  • scratch-gui: teacher-notifications.test.jsx 5 件 + classroom-api 3 件(対象ファイル lint クリーン)
  • headless Playwright(API スタブ): 9/9 チェック(教師 UI)+ 7/7 チェック(admin SPA)

残タスク(マージ前後)

  • stg デプロイ(ClassroomStack-stg / AdminStack-stg)— AWS SSO 再ログイン待ち。cdk synth は両スタックとも検証済み
  • stg での integration 確認(npm run test:integration + verify スクリプト)

Related Issues

Refs #1111(topic branch 集約後に develop PR で Close 予定)

🤖 Generated with Claude Code

smalruby3-editor-bot Bot and others added 5 commits July 25, 2026 16:06
お知らせセンターの土台。ClassroomNotifications テーブル (PK teacherSub /
SK notificationId=createdAt接頭辞) と、先生向けの GET /notifications
(一覧+未読数) / POST /notifications/mark-read (ids省略で全既読) を追加。

設計上の分岐点 (このコミットに立ち戻ればやり直せる):
- D1 置き場所: 汎用お知らせ基盤ではなく classroom スタック所有にした。
  受信者が先生 (teacherSub) で、既存の先生 ID Token 認証をそのまま
  読み出しに使えるため。書き込みは admin スタックが名前規約 import で
  行う (SharedAssignments と同じ N2 維持の形)。代替案: admin スタック
  所有 (先生の読み出し認可を admin 側に作る必要があり不採用)
- D2 既読API: mark-read は notificationIds 省略時に直近50件窓の未読を
  全既読化 (パネルを開いたらバッジが消える UX 前提)。代替案: 1件ずつ
  既読 (クリック時に都度更新。API往復が増えるため不採用)
- D3 保持期間: TTL は書き手 (admin スタック) が付与する。通知は短命な
  案内でありレコードではないため DESTROY / stream なし

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
POST /admin/notifications。SPA からは classroomId だけを送り、宛先の
teacherSub はサーバー側で解決して ClassroomNotifications へ書き込む。
テーブルは classroom スタック所有を名前規約 import (N2) し、この
スタックが単一の書き手 (write-only grant)。全送信を audit() 記録。

設計上の分岐点 (このコミットに立ち戻ればやり直せる):
- D4 宛先指定: teacherSub を admin SPA へ渡さず classroomId で指定。
  admin 投影が authorSub/teacherSub を意図的に隠す既存原則の踏襲。
  代替案: teacherSub 直接指定 (内部IDの露出が増えるため不採用)
- D5 grant: 読みを許可せず grantWriteData のみ。読み出し導線は
  先生側 (classroom API) に限定し、単一ライターの非対称を IAM でも
  表現。代替案: RW grant (送信履歴の admin 表示が必要になったら拡張)
- link.kind は whitelist ('classroom' のみ)。後続 Issue (#1110/#1106)
  で kind を増やす際はここに追加する

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
クラス検索/俯瞰候補から開くクラス詳細の下部に「先生へのお知らせ」
パネルを追加。タイトル+本文を入力し、既存の二段階確認 (arm/confirm)
を通って POST /admin/notifications を呼ぶ。

設計上の分岐点 (このコミットに立ち戻ればやり直せる):
- D6 送信UIの置き場所: クラス詳細 (ClassroomDetail) 内に配置。
  俯瞰の有益候補クリック→詳細→送信、検索→詳細→送信の両動線が
  1 箇所で済むため。代替案: 専用タブ/俯瞰の行内ボタン (対象クラスの
  文脈確認なしに送れてしまうため不採用)
- 既存の mutation 規約 (二段階確認・本文必須で無効化) を踏襲

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
先生モーダルのタイトルバー (× の左隣) に 🔔 ボタン + 未読バッジを
追加。クリックでお知らせ一覧パネルを開き、開いた時点で全既読化。
お知らせの link.kind='classroom' クリックで該当クラスのボード文脈
(クラス選択→課題詳細) へジャンプする。60 秒間隔でポーリング。

設計上の分岐点 (このコミットに立ち戻ればやり直せる):
- D7 アイコン: 絵文字 🔔 を採用。classroom UI は絵文字/テキスト
  バッジが既存慣行 (⚠ など) で新規 SVG アセット不要のため。
  代替案: icon--*.svg (menu-bar の慣行。視覚統一が必要になれば移行)
- D8 既読化: パネルを開いた時点で mark-read (全件)。バッジは即消え、
  未読ドットは取得済み readAt のままなのでその場では見分けられる。
  代替案: アイテムクリックごとに個別既読 (API往復増のため不採用)
- D9 更新間隔: 専用フックで 60 秒ポーリング。既存の 30 秒クラス一覧
  リフレッシュへの相乗りはフック間結合が増えるため不採用
- D10 通知取得エラーは黙殺 (401 のみ再認証へ)。通知障害がクラス管理
  本体を壊さないため
- 配置: logout を right 4.5rem→7.5rem、email を 10.5rem→13.5rem へ
  ずらし、🔔 を 4.5rem (× の隣) に置いた

検証: jest 19 件 + headless Playwright (APIスタブ) 9/9 チェック合格

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- architecture.md: /notifications ルート表 + ClassroomNotifications
  データモデル (単一ライター設計の説明つき)
- source-code.md: 新規ファイル・ハンドラ関数
- ui-ux.md: 1.8 お知らせセンター (data-testid 一覧 + スクリーンショット 0218)
- admin/README.md: 送信フォームの説明 + スクリーンショット 0106
- tools/playwright-verify/verify-notification-center.mjs: API スタブ式の
  リグレッションスクリプト (stg 不要で 9 チェック)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

レビュー (PR #1113) の指摘への対応:

- [Must] 通知タイムスタンプを toLocaleString でローカルタイム表示に
  (ISO slice だと UTC のまま出て日本では 9 時間ずれる)
- [Question] handleToggleNotifications の副作用を state updater の外へ
  (StrictMode/concurrent で updater が再実行されても二重送信しない)
- [FYI] link.kind の whitelist 検証を putNotification 内へ移動
  (呼び出し側ハードコードで dead code だったのを、将来の送信経路
  すべてが通る位置に)
- [FYI] mark-read: body が JSON 配列のとき 400 (暗黙の全既読を防ぐ)、
  id は 200 文字上限 (DynamoDB ValidationException→500 ではなく 400)
- [FYI] classroom Lambda の通知テーブル grant を Query/UpdateItem のみに
  縮小 (PutItem を外し「エディタから偽造不可」を IAM でも担保)
- [FYI] use-teacher-notifications のフックユニットテストを追加 (6件:
  interval 停止・開いたら一度だけ mark-read・401 再認証・リセット)
- [FYI] .claude/rules/infra/smalruby-classroom.md のテーブル一覧の
  ドリフト解消 (4→8 テーブル)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@smalruby3-editor-bot

Copy link
Copy Markdown
Author

敵対レビュー結果(自動レビュー + 対応済み)

diff 全体(infra 2 スタック / admin SPA / scratch-gui)を対象に、正確性・セキュリティ・.claude/rules 準拠・整合性・テスト妥当性の 5 観点で敵対的に検証した。全新規テスト実行 + tsc/eslint/prettier 確認済み。

指摘と対応(コミット 56c226c

分類 指摘 対応
[Must] 通知タイムスタンプが ISO slice で UTC 表示(日本では 9 時間ずれ・日付も前日になりうる) toLocaleString() に変更 ✅
[Question] handleToggleNotifications が state updater 内で副作用(StrictMode/concurrent で二重送信の芽) 副作用を updater の外へ ✅
[FYI] admin の link.kind whitelist 検証が dead code putNotification 内へ移動(将来の送信経路も通る位置) ✅
[FYI] mark-read: body が JSON 配列だと暗黙に全既読 / 巨大 id が 500 配列 body → 400、id 200 文字上限 ✅
[FYI] classroom Lambda に通知テーブルの RW grant(PutItem 含む) Query/UpdateItem のみに縮小(単一ライターを IAM でも担保) ✅
[FYI] フック(interval/既読化ロジック)にユニットテストなし use-teacher-notifications.test.js 6 件追加 ✅
[FYI] rules のテーブル一覧ドリフト(「4 テーブル」のまま) 8 テーブルに更新 ✅

クリーンと確認された点(抜粋)

  • SK(ISO 固定長 + #uuid)の newest-first 並びは正しい(固定長 prefix なので # がクロスタイムスタンプ順序に影響しない)
  • 他の先生の通知の読み取り/既読化は key スキーマ上不可能。createdBy/teacherSub は投影から除外(テストで serialized body を検証)
  • 新ルートはどの既存 regex にも飲み込まれない。admin ルートは prod JWT authorizer を通る
  • N2(admin から classroom スタック不変更)維持、audit あり、4xx パスへのログ追加なし
  • CSS オフセット変更(logout/email の移動)に位置依存の外部利用なし

判定: 指摘対応後 MERGEABLE(stg デプロイ + integration 確認は SSO 再ログイン後のフォローアップ)

@smalruby3-editor-bot
smalruby3-editor-bot Bot merged commit 5faa7ff into topic/classroom-notification-center Jul 25, 2026
21 checks passed
@smalruby3-editor-bot
smalruby3-editor-bot Bot deleted the feature/notification-center-1111 branch July 25, 2026 16:52
@smalruby3-editor-bot

Copy link
Copy Markdown
Author

stg デプロイ・検証完了(2026-07-25)

ClassroomStack-stg / AdminStack-stg をデプロイし、integration テスト(49 件パス・教師フロー含む)+ 実 API のスモークで 3 EPIC の通しを確認:

検証データはクリーンアップ済み(取り下げ + アーカイブ、stg TTL 1 日)。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants