Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
b2118c7
feat(classroom-api): shared assignment library store + API (#1068)
smalruby3-editor-bot[bot] Jul 18, 2026
23e01ef
Merge pull request #1075 from smalruby/feature/assignment-sharing-1068
smalruby3-editor-bot[bot] Jul 18, 2026
ea8e4b5
feat(classroom): shared assignment publish form (#1069)
smalruby3-editor-bot[bot] Jul 18, 2026
d26f3e7
Merge pull request #1076 from smalruby/feature/assignment-sharing-1069
smalruby3-editor-bot[bot] Jul 18, 2026
19ba288
feat(classroom): shared assignment catalog + import UI (#1070)
smalruby3-editor-bot[bot] Jul 18, 2026
f453e82
Merge pull request #1077 from smalruby/feature/assignment-sharing-1070
smalruby3-editor-bot[bot] Jul 18, 2026
24dc978
feat(classroom-ops): shared assignment moderation CLI + runbook (#1071)
smalruby3-editor-bot[bot] Jul 18, 2026
aa86d50
Merge pull request #1078 from smalruby/feature/assignment-sharing-1071
smalruby3-editor-bot[bot] Jul 18, 2026
2fb34da
docs(assignment-sharing): feature docs + e2e regression script (#1072)
smalruby3-editor-bot[bot] Jul 18, 2026
adb744e
Merge pull request #1079 from smalruby/feature/assignment-sharing-1072
smalruby3-editor-bot[bot] Jul 18, 2026
1e06e2f
fix(classroom): omit empty ExpressionAttributeNames on mine=1 shared …
smalruby3-editor-bot[bot] Jul 18, 2026
70b77b4
Merge pull request #1090 from smalruby/fix/shared-assignments-mine-500
smalruby3-editor-bot[bot] Jul 18, 2026
69fed50
Merge remote-tracking branch 'origin/develop' into feature/assignment…
takaokouji Jul 19, 2026
61ff68b
feat(classroom): 課題詳細の参加コードを1枚のカードに集約し配信ボタンを明確化
smalruby3-editor-bot[bot] Jul 19, 2026
9e3e292
feat(classroom): 課題名と参加コードを均等幅2カラムに分けて崩れを防ぐ
smalruby3-editor-bot[bot] Jul 20, 2026
b8bce7f
feat(classroom): クラス人数変更を課題に伝播 + 課題詳細レイアウト洗練
smalruby3-editor-bot[bot] Jul 20, 2026
00f2933
fix(classroom): 参加コードカードを常に2行に収め課題名と高さを揃える
smalruby3-editor-bot[bot] Jul 20, 2026
85cd49f
fix(classroom): 参加コードのボタンを常にアイコン化し崩れを防ぐ
smalruby3-editor-bot[bot] Jul 20, 2026
49175ed
feat(classroom): 保存期限の警告をインライン化し一覧にも削除予告とDLを追加
smalruby3-editor-bot[bot] Jul 20, 2026
793d4c6
feat(classroom): 課題配信画面を基本レイアウト(パンくず/右下プライマリー)に統一
smalruby3-editor-bot[bot] Jul 20, 2026
bf09de2
fix(classroom): 課題配信画面の左マージンを課題詳細に合わせる
smalruby3-editor-bot[bot] Jul 20, 2026
4660b24
fix(classroom): 課題詳細のクラス名を課題一覧と同じ太字に揃える
smalruby3-editor-bot[bot] Jul 20, 2026
eb761bf
feat(classroom): 共有課題に合言葉による限定公開を追加(backend, #1109)
smalruby3-editor-bot[bot] Jul 20, 2026
3d77687
feat(classroom): 合言葉の lookup / import API クライアントを追加(#1109)
smalruby3-editor-bot[bot] Jul 20, 2026
f33af05
docs(classroom): 合言葉による限定公開のAPI/モデルを追記(#1109)
smalruby3-editor-bot[bot] Jul 20, 2026
a70041f
feat(classroom): 共有導線をボード各行に移し合言葉限定公開ステップを追加(#1109)
smalruby3-editor-bot[bot] Jul 20, 2026
4398a5e
feat(classroom): 合言葉で取り込みUIを追加(#1109・受け取る側)
smalruby3-editor-bot[bot] Jul 20, 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
87 changes: 87 additions & 0 deletions docs/assignment-sharing/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# みんなの課題(共有課題ライブラリ)

> **🆕 Smalruby 独自** — upstream に存在しない、Smalruby のために新規追加された機能

先生が自分で作成した課題(説明ページ + スタータープロジェクト + 補足資料 URL)を**インターネットを通じて全国の先生に共有し、再利用できる**仕組み。他の先生は共有された課題を閲覧・絞り込みし、自分のクラスに取り込むとスタータープロジェクトごと読み込まれ、そのまま授業を進められる。

設計の正典: EPIC #1066 / スパイク #1067 の Decision Log(D1〜D12)。

## 目次

| ドキュメント | 内容 |
|-------------|------|
| 本 README | 機能全体像・主要ファイル・設定 |
| [運用手順書](operations.md) | 通報対応(モデレーション)の運用者向け手順 |
| [../classroom/architecture.md](../classroom/architecture.md) | API ルート・データモデル(「みんなの課題」セクション) |
| [../classroom/testing.md](../classroom/testing.md) | data-testid 一覧(`shared-*`) |

## 全体像

```text
先生A(共有する側) 先生B(使う側)
課題詳細 →「この課題を共有」 課題ボード →「みんなの課題からさがす」
タイトル・属性(学校種×学年×教科×タグ) カタログ(新着順・絞り込み)
補足資料URL(指導案など・https のみ) → 詳細(説明ページ・© 表示名 / CC BY 4.0)
表示名・所属 / CC BY 4.0 同意 「このクラスに取り込む」→ 授業開始
```

- **閲覧・投稿とも先生ログイン必須**(D1)
- **ライセンスは CC BY 4.0 に統一**(D2): 投稿時に同意、クレジットは表示名。取り込み後の改変・授業利用は自由
- **共有はスナップショット**: 投稿後にクラス側の課題を変えても共有側は変わらない(更新は「自分の投稿」から上書き、D10)。取り込みも独立コピー(元の更新に追従しない)
- **共有データは保存期限(TTL)の対象外**(D7・永続)。クラス機能の 90 日 TTL とは分離
- **プロフィールは最小限**(D6): 表示名(必須)+ 所属表記(任意)のみ。メール・実名は保持しない
- **補足資料 URL**(D4): https のみ。入力時に「学習指導案・授業スライドなど授業の進め方がわかる資料(Google ドライブ / ドキュメントの閲覧リンク推奨)」と明示し、閲覧側は外部ドメイン名付きの確認を挟む
- **モデレーションは事後対応型**(D3): 通報 → 運用 CLI で unpublish([operations.md](operations.md))。Admin SPA(EPIC #1073)完成後は管理画面に移行

## 画面

### 共有フォーム(課題詳細 →「この課題を共有」)

![共有フォーム](screenshots/0101-share-form.png)

### カタログ(課題ボード →「みんなの課題からさがす」)

![カタログ](screenshots/0102-catalog.png)

### 詳細プレビュー(クレジット・補足資料リンク・取り込み)

![詳細プレビュー](screenshots/0103-detail.png)

### 取り込み完了(ボードに新しい課題として出現)

![取り込み完了](screenshots/0104-imported.png)

## 主要ファイル

### バックエンド(infra/smalruby-classroom)

| ファイル | 役割 |
|---------|------|
| `lambda/handler.ts` | `/shared-assignments` 系 7 エンドポイント(共有・一覧・詳細・取り込み・更新・取り下げ・通報)、タクソノミ/URL/プロフィールのバリデータ、`buildSharedSnapshot` |
| `lambda/shared-admin-lib.ts` | 通報対応 CLI の純粋ロジック |
| `bin/shared-assignments-admin.ts` | 通報対応 CLI(dry-run 既定) |
| `lib/classroom-stack.ts` | `SharedAssignments`(TTL なし・prod RETAIN + PITR・GSI×2)/ `SharedAssignmentReports`(TTL 90日)/ 専用バケット(lifecycle なし) |

### フロントエンド(packages/scratch-gui)

| ファイル | 役割 |
|---------|------|
| `src/components/classroom-modal/shared-assignment-form.jsx` | 共有フォーム(属性・URL ガイダンス・CC BY 同意) |
| `src/components/classroom-modal/shared-assignment-catalog.jsx` | カタログ(一覧・絞り込み・詳細・取り込み・自分の投稿・通報) |
| `src/containers/use-shared-assignments.js` | 共有/カタログの状態管理フック |
| `src/lib/shared-assignment-taxonomy.js` | 学校種×教科の語彙(サーバーのミラー)+ parseTags |
| `src/lib/shared-author-profile.js` | プロフィールの localStorage 永続化 |
| `src/lib/classroom-api.js` | API クライアント(shared 系 7 メソッド) |

## 設定・データ永続化

| 種別 | キー / 変数 | 内容 |
|------|------------|------|
| localStorage | `smalruby:sharedAuthorProfile` | 表示名・所属表記の記憶 |
| env(Lambda) | `SHARE_DAILY_LIMIT`(既定 10)/ `REPORT_DAILY_LIMIT`(既定 20) | 1日あたりの共有 / 通報回数制限 |
| env(Lambda) | `SHARED_STARTER_MAX_BYTES`(既定 50MB) | 共有スターターの容量上限 |

## テスト

- unit: `lambda/tests/handler-shared-assignments.test.ts`(API 25件)/ `shared-admin-lib.test.ts`(CLI 6件)/ GUI `shared-assignment-form/catalog/taxonomy/author-profile`(24件)
- E2E: `tools/playwright-verify/verify-assignment-sharing.mjs`(共有 → カタログ → 取り込み → 自分の投稿の通し。`LOCALE=ja-JP` で日本語スクリーンショット)
64 changes: 64 additions & 0 deletions docs/assignment-sharing/operations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# みんなの課題 運用手順書(通報対応)

> **🆕 Smalruby 独自** — みんなの課題(共有課題ライブラリ、EPIC #1066)の通報対応を行う運用者向け手順。

対象読者: AWS クレデンシャル(SSO)を持つ運用者。設計の背景は EPIC #1066 / スパイク #1067 の Decision Log(D3: 事後対応型モデレーション)。

> **将来**: Admin SPA(EPIC #1073)が完成したら通報対応は管理画面に移行する。本 CLI はそれまでの最小ツール兼フォールバック。

## モデレーション方針

- **事後対応型**: 投稿は即公開(投稿には先生ログイン + CC BY 4.0 同意が必須)。先生からの通報を受けて運用者が確認・対応する
- **物理削除はしない**: 対応は `unpublish`(`status: 'unlisted'` = カタログから非表示)。誤対応は `republish` で復元できる
- **投稿者への連絡手段は保持していない**(D6: 個人情報最小化のため email を持たない)。対応は非公開化のみで完結させる
- 通報レコードは 90 日で自動削除(TTL)。通報者の識別子(reporterSub)は悪用対策の内部データで、CLI にも表示されない

## 前提条件

```bash
cd infra/smalruby-classroom

# ステージを .env symlink で選択(deploy と同じ流儀)
ls -la .env # -> .env.stg または .env.prod

# AWS クレデンシャル(コンテナ内 SSO)
aws sso login --sso-session smalruby --use-device-code # 失効時のみ
export AWS_PROFILE=smalruby AWS_REGION=ap-northeast-1
eval "$(aws configure export-credentials --profile smalruby --format env)"
```

## 通報対応フロー

```text
1. 通報キューを確認
npx ts-node bin/shared-assignments-admin.ts list-reports
→ 通報の多い投稿から順に、タイトル・status・通報理由が並ぶ

2. 内容を確認
npx ts-node bin/shared-assignments-admin.ts show <sharedId>
→ 説明ページ全文・補足 URL・投稿者表示名・属性を表示
(補足 URL の先はブラウザで確認。リンク先は投稿者管理のため慎重に)

3. 判断
├─ 問題あり(下記基準)→ 非公開化:
│ npx ts-node bin/shared-assignments-admin.ts unpublish <sharedId> # dry-run
│ npx ts-node bin/shared-assignments-admin.ts unpublish <sharedId> --apply # 実行
└─ 問題なし → 対応不要(通報は 90 日で自動消滅)

4. 誤対応の復元
npx ts-node bin/shared-assignments-admin.ts republish <sharedId> --apply
```

## 非公開化の判断基準(目安)

| 該当 | 対応 |
|------|------|
| 個人情報(生徒名・学校の内部情報等)が含まれる | 即 unpublish |
| 補足 URL が課題と無関係・不適切なサイト | 即 unpublish |
| 著作権侵害の疑い(第三者の教材の丸写し等) | unpublish して様子見 |
| 授業として低品質・単なる好みの問題 | 対応しない(通報理由に返答する術はない) |

## 補足

- 取り込み済みの課題は各先生のクラス内のスナップショットなので、**unpublish しても既に取り込んだ先生には影響しない**
- 投稿の実体(S3 `shared/{sharedId}/`)は unpublish でも残る(復元可能性のため)。完全削除が必要な法的要請の場合のみ AWS コンソールで S3 オブジェクトと DynamoDB アイテムを手動削除する
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions docs/classroom/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ Smalruby Classroom は、日本の学校の授業で Smalruby を使うための
| [テスト](testing.md) | data-testid 一覧、Playwright / 結合テスト |
| [Microsoft 認証](microsoft-authentication.md) | MSAL.js 統合、サイレント再認証、Azure Portal 設定 |
| [運用手順書](operations.md) | 「消えたクラスを復旧してほしい」問い合わせ対応(アーカイブ復元の案内 / 期限切れ復元スクリプト) |
| [みんなの課題](../assignment-sharing/README.md) | 全国の先生と課題を共有・再利用する機能(別 feature ディレクトリ) |

![Smalruby メニューバーの「クラス」ボタン](screenshots/0101-menu-bar.png)

Expand Down
24 changes: 24 additions & 0 deletions docs/classroom/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ sequenceDiagram
| **DynamoDB** | `ClassroomMemberships-{stage}` | メンバー (生徒) 情報(Streams: OLD_IMAGE) |
| **DynamoDB** | `ClassroomSubmissions-{stage}` | 提出情報(Streams: OLD_IMAGE) |
| **DynamoDB** | `ClassroomGroups-{stage}` | クラス(学級)情報(Streams: OLD_IMAGE) |
| **DynamoDB** | `SharedAssignments-{stage}` | みんなの課題(TTL なし・prod RETAIN + PITR) |
| **DynamoDB** | `SharedAssignmentReports-{stage}` | みんなの課題の通報(TTL 90日) |
| **S3** | `smalruby-shared-assignments-{stage}` | 共有課題のスナップショット(lifecycle なし = 永続・prod RETAIN) |
| **S3** | `smalruby-classroom-submissions-{stage}` | 提出ファイル (.sb3, サムネイル, スクリーンショット) + `ddb-archive/` スナップショット。lifecycle = `ARCHIVE_RETENTION_DAYS`(既定 365 日) |
| **Route53** | A レコード | カスタムドメイン |
| **ACM** | SSL 証明書 | HTTPS |
Expand Down Expand Up @@ -185,6 +188,27 @@ sequenceDiagram
| `POST` | `/classrooms/{id}/duplicate` | クラス(授業)の複製。課題コンテンツの S3 オブジェクトもコピー。`groupId` / `className` / `assignmentName` を上書き可。メンバー・提出は複製しない |
| `POST` | `/classrooms/{id}/evaluate` | AI 評価支援。静的解析結果(シグナル + 擬似コード)を Anthropic API にリレーし、`mode: grade` は S/A/B/C 案 + 根拠 + needsReview、`mode: comment` は生徒向けポジティブコメント下書きを返す。1リクエスト最大10提出(API GW の30秒制限対策、クライアントがチャンク分割)。先生ごとにレート制限(既定 60回/時) |

### みんなの課題 (共有課題ライブラリ・先生 ID Token 認証)

全国の先生が課題(説明ページ + スターター + 補足資料 URL)を共有・再利用する機能(EPIC #1066。設計の正典は spike #1067)。

| Method | Path | 説明 |
|--------|------|------|
| `POST` | `/shared-assignments` | 課題を共有。`visibility`(省略時 `public`)で公開範囲を選ぶ。`public`=みんなの課題カタログ(CC BY 4.0 同意・属性・著者名 必須)。`limited`=合言葉限定公開(#1109。同意・属性・著者名は任意で、参加コード同型の合言葉を発行)。スターター 50MB 上限・10件/日制限 |
| `GET` | `/shared-assignments` | カタログ一覧(新着順・`schoolLevel`/`subject`/`grade`/`tag` で絞り込み・`cursor` ページネーション・`mine=1` で自分の投稿一覧)。**公開カタログは限定公開を除外**。`mine=1` は自分の合言葉を含む |
| `GET` | `/shared-assignments/{id}` | 詳細(ページ・画像/スターターの presigned URL・投稿者表示名。authorSub は返さない)。**限定公開は sharedId を知っていても非著者には 404**(合言葉ルックアップ経由でのみアクセス) |
| `POST` | `/shared-assignments/lookup` | 合言葉プレビュー(#1109)。`{passcode}` → summary(**sharedId は返さない**) |
| `POST` | `/shared-assignments/import-by-passcode` | 合言葉で取り込み(#1109)。`{passcode, groupId, assignmentName?}` → 自分のクラスに課題として取り込み。sharedId を露出しない内輪取り込み |
| `POST` | `/shared-assignments/{id}/import` | 自分のクラス(groupId)に課題として取り込み(**全体公開のみ**。限定公開は import-by-passcode を使う)。S3 逆コピー + reuseCount 増分 |
| `PATCH` | `/shared-assignments/{id}` | 更新(投稿者本人のみ。メタデータ + `classroomId` 指定で内容の再スナップショット=上書き)。**`visibility` を `limited`→`public` に広げる時は CC BY 同意・属性・著者名を必須化** |
| `DELETE` | `/shared-assignments/{id}` | 取り下げ = `status: 'unlisted'`(物理削除しない。本人のみ) |
| `POST` | `/shared-assignments/{id}/report` | 通報(理由必須・20件/日制限。reporterSub は内部保持のみ) |

- **公開範囲(#1109)**: 項目は `visibility`(`public`/`limited`)を持つ。#1109 以前の項目は属性を持たず `public` とみなす(後方互換)。`limited` は `passcode`(合言葉)を持ち、公開カタログには出ない。「限定公開(合言葉・内輪)→ Admin が把握 → 推薦 → 全体公開」パイプラインの土台
- データ: `SharedAssignments{suffix}`(**TTL なし・prod は RETAIN + PITR**。GSI: `status-createdAt-index` / `authorSub-createdAt-index` / `passcode-index`(合言葉ルックアップ・#1109))、`SharedAssignmentReports{suffix}`(TTL 90日)
- ファイル: 専用バケット `smalruby-shared-assignments{suffix}`(**lifecycle なし = 永続**、`shared/{sharedId}/` プレフィックス)。クラス側の保存期限と完全に分離
- 共有/取り込みの実体は既存 duplicate と同じ S3 サーバー側コピー(クロスバケット)

### 生徒用 (認証不要 / Session Token)

| Method | Path | 認証 | 説明 |
Expand Down
34 changes: 34 additions & 0 deletions docs/classroom/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,40 @@ Playwright MCP および Selenium integration tests で使用する `data-testid
| `classroom-board-download-class` | button | ボードの「全課題の提出物をダウンロード」(active + アーカイブ済みを 1 つの zip に) |
| `classroom-retention-banner` | div | 課題詳細の保存期限アラートバナー(30 日以下で表示) |
| `classroom-retention-banner-download` | button | バナー内の「全作品ダウンロード」 |
| `classroom-share-assignment` | button | 課題詳細の「この課題を共有」(みんなの課題フォームを開く) |
| `shared-form` | form | みんなの課題の共有フォーム |
| `shared-form-title` / `shared-form-summary` | input | タイトル / 短い説明 |
| `shared-form-level` | select | 学校種 |
| `shared-form-subject` | select | 教科(制御語彙。学校種=その他のときは `shared-form-subject-free` input) |
| `shared-form-grade-{n}` | checkbox | 対象学年 |
| `shared-form-tags` | input | タグ(カンマ区切り・最大5) |
| `shared-form-lesson-count` | input | 想定コマ数 |
| `shared-form-url` | input | 補足資料 URL(https のみ。ガイダンス=`shared-form-url-hint`、エラー=`shared-form-url-error`) |
| `shared-form-author-name` / `shared-form-author-affiliation` | input | 表示名 / 所属表記(localStorage 記憶) |
| `shared-form-consent` | checkbox | CC BY 4.0 同意(未チェックだと送信不可) |
| `shared-form-submit` / `shared-form-cancel` | button | 共有する / キャンセル |
| `shared-form-success` | p | 公開完了メッセージ(© 表示名 / CC BY 4.0) |
| `classroom-board-shared-catalog` | button | ボードの「みんなの課題からさがす」 |
| `shared-catalog` | div | みんなの課題カタログ(ボード内に展開) |
| `shared-catalog-close` | button | カタログを閉じる |
| `shared-catalog-tab-all` / `shared-catalog-tab-mine` | button | すべて / 自分の投稿 タブ |
| `shared-catalog-filter-level/subject/grade/tag` | select/input | 絞り込み(学校種・教科・学年・タグ) |
| `shared-catalog-filter-apply` | button | 絞り込み実行 |
| `shared-catalog-list` / `shared-catalog-item-{id}` / `shared-catalog-open-{id}` | ul/li/button | カード一覧(属性バッジ・投稿者・取り込み回数) |
| `shared-catalog-load-more` | button | 次ページ読み込み(cursor があるときのみ) |
| `shared-catalog-empty` | p | 空メッセージ |
| `shared-catalog-detail` | div | 詳細プレビュー |
| `shared-detail-close` | button | 一覧に戻る |
| `shared-detail-credit` | p | 「© 表示名(所属) / CC BY 4.0」クレジット行 |
| `shared-detail-starter` | p | スターター付きの説明 |
| `shared-detail-url` | button | 補足資料リンク(クリックで確認表示) |
| `shared-detail-url-confirm` / `shared-detail-url-open` / `shared-detail-url-cancel` | span/a/button | 外部ドメイン名付き確認 →「開く」(rel=noopener・新規タブ) |
| `shared-detail-import` | button | このクラスに取り込む(published のみ表示) |
| `shared-detail-report` | button | 通報フォームを開く(他人の投稿のみ) |
| `shared-report-form` / `shared-report-reason` / `shared-report-submit` | div/textarea/button | 通報理由(必須)と送信 |
| `shared-report-sent` | p | 通報完了メッセージ |
| `shared-detail-unlist` / `shared-detail-republish` | button | 自分の投稿の取り下げ / 再公開 |
| `shared-import-success` | p | 取り込み完了メッセージ(ボード上) |
| `classroom-breadcrumbs` | nav | パンくず(クラス一覧 > 課題一覧 > 課題詳細) |
| `classroom-breadcrumb-class-list` / `classroom-breadcrumb-assignments` | button | パンくずリンク |
| `classroom-board-create-name` / `classroom-board-create-submit` | input / button | インライン課題作成(課題名のみ) |
Expand Down
Loading
Loading