Skip to content

fix(classroom): 連携した先生以外の Google Classroom 共有を実態で判定する (#1184) - #1185

Open
smalruby3-editor-bot[bot] wants to merge 3 commits into
developfrom
topic/autopilot-1184
Open

smalruby3-editor-bot[bot] wants to merge 3 commits into
developfrom
topic/autopilot-1184

Conversation

@smalruby3-editor-bot

Copy link
Copy Markdown

Summary

Google Classroom への共有は 押した先生自身の Google アクセストークンで courseWork を作成するため、Google はその先生がそのコースの教師であることを要求する。Smalruby は共同担当にも共有ボタンを押させていたので、GC コースに入っていない共同担当が押すと生の google ClassroomAPI error(実体は 404)になっていた。

Smalruby 上の役割(owner / co-teacher)で決め打ちせず、GC コースの教師かどうかという実態で判定するようにした。同じコースに GC 共同教師として入っている先生は従来どおり共有できる。

Changes Made

サーバー (infra/smalruby-classroom)

  • GET /classroom-groups/{groupId}/google-course-access を追加。caller のトークンで courses/{courseId}/teachers/me を引き、200 → teacher / 404 → notTeacher / それ以外 → unknown(判定不能)を返す。linkedByEmail も返す
  • GC コースを紐付けた先生を group の googleClassroomLinkedBy({sub, email})に記録(クラス作成時・更新時の両方。解除時はクリア)。GET /classroom-groups は表示用に googleClassroomLinkedByEmail を返す。既存データはクラス作成者(teacherSub)にフォールバック(メール不明)
  • courseWork 作成時の 404 を新しい reason googleNotCourseTeacher にマップ(GoogleAPIError に operation を持たせた)。他の経路の 404 は従来どおり googleCourseNotFound
  • サーバー側で「連携した先生以外を一律拒否」はしていない(GC 共同教師の正当なケースを壊さないため)

クライアント (packages/scratch-gui)

  • src/lib/classroom-google-share.js(新規・純粋): isGoogleShareBlocked() と googleShareBlockedMessage()
  • 共有ボタンは notTeacher と判明したときだけ disabled。理由を classroom-post-assignment-blocked に日本語で表示(「このクラスの Google Classroom は ◯◯ 先生が連携しています。共有は ◯◯ 先生に依頼してください。」/メール不明時は「連携した先生」と一般名詞)
  • 事前判定は すでに取得済みの Google アクセストークンがあるときだけ 実行(getCachedClassroomAccessToken() を追加)。判定のために同意ポップアップを出さない。判定できないときはボタンを無効化せず、失敗時に googleNotCourseTeacher の文言で同じ趣旨を出す
  • 文言は ja.js / ja-Hira.js の両方に追加

Test Coverage

  • infra/smalruby-classroom/lambda/tests/handler-google-course-access.test.ts(新規): reason マッピング(operation 付き)、buildGoogleClassroomLinkedBy / readGoogleClassroomLinkedBy のフォールバック、classifyCourseTeacherFailure(404 のみ notTeacher)
  • test/unit/lib/classroom-google-share.test.js(新規): 判定と文言
  • test/unit/containers/classroom-error-utils.test.js: googleNotCourseTeacher の翻訳(googleCourseNotFound と別文言・生の NOT_FOUND を出さない)
  • test/unit/components/teacher-class-detail.test.jsx: disabled / 理由表示 / unknown・未実行では無効化しない

ローカル結果: classroom lambda 23 suites 346 tests pass、scratch-gui の関連 unit test pass、npm run lint(eslint + prettier)clean、tsc --noEmit clean。

Docs

  • docs/classroom/architecture.md(ルート・reason・データモデル・判定方針の節)
  • docs/classroom/ui-ux.md / docs/classroom/testing.md / docs/classroom/source-code.md
  • .claude/rules/scratch-gui/e2e-test.md の data-testid 一覧

Deploy

サーバー変更は後方互換(ルート追加 + 任意フィールド追加)。stg へ載せるには PR に deploy-stg ラベルを付ける。prod への反映は人間が実施。

Related Issues

Closes #1184
Refs #1175

… course

Sharing posts course work with the pressing teacher's own Google token, so Google
requires them to be a teacher of the linked course. Smalruby let any class
co-teacher press the button, so one who is not in the course hit an opaque
`google ClassroomAPI error`.

Judge by the fact, not by the Smalruby role: a new
GET /classroom-groups/{groupId}/google-course-access probes courses/{id}/teachers/me
with the caller's token (200=teacher, 404=notTeacher, anything else=unknown). A GC
co-teacher of the same course still passes. The UI disables the share button only on
a proven notTeacher and says who to ask; the probe uses an already-granted token only,
so it never opens a consent popup.

Groups now record googleClassroomLinkedBy
{sub,email} on link/re-link (falling back to the class owner for old rows) so the
message can name them, and a 404 from courseWork creation maps to the new
googleNotCourseTeacher reason instead of googleCourseNotFound.

Closes #1184

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@smalruby3-editor-bot smalruby3-editor-bot Bot added the 🤖 autopilot autopilot が処理対象にした Issue/PR(daemon が付与) label Oct 10, 2026
@smalruby3-editor-bot

smalruby3-editor-bot Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
Author

🤖 autopilot status

field value
Status Review
AI Status —
HITL Yes
Size middle

Linked issue #1184. Maintained by autopilot (single writer); do not edit.

@github-actions

Copy link
Copy Markdown

🚀 Preview deployed: https://smalruby.jp/smalruby3-editor/topic/autopilot-1184/

… screen

The share pre-flight verdict carried a groupId but nothing compared it, so a
"not a course teacher" answer about the previously selected class disabled the
share button for the class now shown (the probe is a network round trip and
answers can arrive out of order, making it stick).

The "ask the linking teacher" note was also rendered without the conditions the
share button itself has, so an assignment already posted to Google Classroom
showed its "open it" link next to a note telling the teacher to get it shared.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@smalruby3-editor-bot

Copy link
Copy Markdown
Author

[Must] 事前判定の結果が「どのクラスについての答えか」を照合しておらず、別クラスの notTeacher が今表示中のクラスの共有ボタンを無効化する(修正済み: 16891fa)

use-google-classroom.js は判定結果に groupId を持たせているのに、isGoogleShareBlocked() も teacher-class-detail.jsx もそれを見ていなかった(status だけ)。

const isGoogleShareBlocked = (access) => Boolean(access) && access.status === GOOGLE_COURSE_ACCESS.NOT_TEACHER;

handleCheckGoogleCourseAccess は、コース紐付け済み + トークンありの場合は状態をクリアせずに await に入るので、クラス A(notTeacher)→ クラス B と切り替えると、B の応答が返るまで B の共有ボタンが「A の連携先生に依頼してください」と出たまま無効になる。A→B→A のように素早く切り替えて応答が前後した場合はその状態のまま残る(notTeacher が最後に勝つと、本来共有できるクラスで共有できなくなる)。「判定できないときはブロックしない」という本 PR の設計意図と逆向きの失敗。

再現確認(修正前):

A: button disabled by stale verdict = true
  group: {groupId: 'gB', ...}  /  googleCourseAccess: {groupId: 'gA', status: 'notTeacher'}

対応: isGoogleShareBlocked(access, groupId) に変更し、access.groupId === groupId のときだけブロックするようにした。回帰テストを classroom-google-share.test.js(別クラスの判定を無視 / groupId 無しの判定を無視 / 画面にクラスが無い)と teacher-class-detail.test.jsx(ignores a verdict that was about a different class)に追加。

@smalruby3-editor-bot

Copy link
Copy Markdown
Author

[Must] 共有ボタンが無い場面でも「共有できない理由」が出る(配信済み課題で「依頼してください」が出る)(修正済み: 16891fa)

classroom-post-assignment-blocked の表示条件が gcShareBlocked だけで、共有ボタン自身の表示条件(selectedClassroom.googleClassroomCourseId || group.googleClassroomCourseId かつ googleClassroomAlternateLink が無い)を踏襲していない。

その結果、すでに Google Classroom に配信済みの課題(ボタンが「Google Classroom で確認」リンク classroom-view-assignment に切り替わっている状態)を、GC 教師ではない共同担当が開くと、

Google Classroom で確認 ←リンク
このクラスの Google Classroom は ◯◯ 先生が連携しています。共有は ◯◯ 先生に依頼してください。

と、**もう共有済みのものを「共有を依頼してください」**と案内する。配信済み課題を共同担当が見るのは通常運用なので頻度は低くない。

再現確認(修正前):

B: orphan note present = true
  selectedClassroom.googleClassroomAlternateLink = 'https://classroom.google.com/c/x/a/y'

対応: gcShareAvailable(共有ボタンが出る条件)を切り出して gcShareBlocked に掛け合わせた。回帰テスト does not explain how to share an assignment that is already shared を追加。

@smalruby3-editor-bot

Copy link
Copy Markdown
Author

[Question] 「コースが削除された」場合も notTeacher と断定するため、連携した本人が「連携した先生に依頼してください」=自分自身に依頼しろ、と言われる

Google は 削除された / アクセスできないコースにも 404 を返す。本 PR はその 404 を 2 箇所で「教師ではない」と断定している:

  • googleApiErrorResponse(404, …, 'postCourseWork') → googleNotCourseTeacher
  • classifyCourseTeacherFailure(404) → notTeacher(事前判定でボタンを disabled に)

連携した先生本人のコースが GC 側で削除された場合(年度末の片付けなどで起こりうる)、その先生は:

  • 事前判定: 共有ボタンが disabled になり「このクラスの Google Classroom は {自分のメールアドレス} 先生が連携しています。共有は {自分} 先生に依頼してください。」(handleGetGoogleCourseAccess は自分が連携者なら linkedByEmail を null にしますが、クライアントが group.googleClassroomLinkedByEmail にフォールバックするので結局自分のアドレスが出ます)
  • 失敗経路: 「…クラスの先生ではないため共有できません。連携した先生に共有を依頼してください。」

となり、自分に依頼しろと言われて出口がない。旧 googleCourseNotFound の文言は「クラスが削除されたか、…先生ではない可能性があります」と両方の可能性を示していたので、ここだけ情報量が落ちている。

Google 側から 2 つを区別する手段は無い(どちらも 404 で隠される)ので、取りうる対応は文言で両方に触れることくらいだと思いますが、classroom-error-utils.test.js の expect(msg).not.toContain('deleted') は意図的に削除ケースを除いたアサーションに見えます。設計判断が割れるところなので、こちらでは直さず判断をお任せします。

参考: 「連携した先生=自分」のときだけ別文言にする案も、クライアントは自分が連携者かを判定できないので(上記フォールバックのため linkedByEmail が null になるのは「既存データでメール不明」のときだけ)、サーバーが isSelf を返す等の対応が要ります。

@smalruby3-editor-bot

Copy link
Copy Markdown
Author

[Question] docs/classroom/source-code.md の Lambda ハンドラー一覧に handleGetGoogleCourseAccess() が無い(修正済み: 16891fa)

同ファイルのクライアント側ファイルツリーには classroom-google-share.js を追加しているのに、すぐ下の「lambda/handler.ts の関数一覧」表には新ハンドラーが載っていなかった。handleListGoogleCourses() / handlePostAssignment() が載っている表なので、GC 系の新ハンドラーもここにあるのが自然。1 行なのでこちらで追加した。

(この表は handleMigrateGroups() 等も載っていない不完全な一覧なので、網羅性の不変条件というよりは近傍との一貫性の話です。)

@smalruby3-editor-bot

Copy link
Copy Markdown
Author

[FYI] disabled なボタンの title は多くのブラウザで出ない(コードは変更していません)

<button data-testid="classroom-post-assignment" disabled={gcShareBlocked} title={gcShareBlockedText || null}>

Chrome / Safari は無効化されたフォームコントロールでポインタイベントを発火しないため、ネイティブツールチップは基本的に表示されない。gcShareBlockedText が出るのは実質 classroom-post-assignment-blocked の本文だけで、title は冗長。害は無いので残して問題ありませんが、ツールチップを当てにした UX にはなっていない点だけ共有。

@smalruby3-editor-bot

Copy link
Copy Markdown
Author

[FYI] 事前判定は選択中クラスの GC 連携を張り替えても追従しない(コードは変更していません)

use-teacher-classroom.js の effect の依存は [selectedGroupId, handleCheckGoogleCourseAccess] なので、同じクラスを選択したまま GC コースを紐付け直した / 解除した場合は再判定されない。

ただし倒れる向きは安全側:

  • 紐付け直し → 判定が古いままでも、張り替えた本人は teacherId=me の一覧から選んでいるので GC 教師。かつ古い判定は null のまま(紐付け前はコース無しで null)→ ボタンは有効。
  • 解除 → googleClassroomCourseId が消えるので共有ボタン自体が出ない。

実害は無さそうなので現状維持で良いと思いますが、依存が id だけである意図(コメント済み)の副作用として記録しておきます。

あわせて、linkedByEmail の出どころが 2 つある点も共有: GET …/google-course-access は呼び出し元自身が連携者のとき null を返す一方、GET /classroom-groups の googleClassroomLinkedByEmail は常に返すため、クライアント側で || で繋いでいます(googleCourseAccess.linkedByEmail || group.googleClassroomLinkedByEmail)。結果として「自分が連携者」のケースでも group 側の値が拾われます。今回の文言では影響しませんが、サーバーの隠蔽が効いていないことになります。

@smalruby3-editor-bot

Copy link
Copy Markdown
Author

🤖 敵対的レビュー完了。指摘 6 件(Must 2 / Question 2 / FYI 2)。Must は修正済み。

Must 2 件(別クラスの事前判定結果の取り違え / 配信済み課題への「共有を依頼してください」表示)はどちらも再現を確認したうえで 16891fa で修正し、回帰テストを追加しました。Question は 1 件修正(docs の関数一覧)、1 件は設計判断が割れるため人間の判断に委ねます(GC コース削除時も "教師ではない" と断定する件)。

ローカル確認: npm run lint(eslint + prettier)clean、classroom-google-share / teacher-class-detail / classroom-error-utils の 43 tests pass、classroom lambda の handler-google-course-access 17 tests pass、tsc --noEmit clean。

@smalruby3-editor-bot smalruby3-editor-bot Bot added the 🙋 HITL 人間の対応待ち(Project HITL=Yes の投影) label Oct 10, 2026
@smalruby3-editor-bot
smalruby3-editor-bot Bot marked this pull request as ready for review October 10, 2026 06:29
@smalruby3-editor-bot

Copy link
Copy Markdown
Author

[Must] 事前判定が「すでに Google トークンを持っている」ときしか走らないため、この Issue が対象にしている先生をちょうど素通りさせます。

prod で #1175 のログ強化を反映して実地検証した結果、状況が確定しました。

Google Classroom API error {
  method: 'POST',
  path: 'courses/797934813085/courseWork',
  httpStatus: 404,
  googleStatus: 'NOT_FOUND',
  googleMessage: 'Requested entity was not found.'
}
  • 連携した先生(A)は同じクラスに問題なく配信できる → コースは生きており、404 は「コース削除」ではなく「押した先生がそのコースの教師ではない」。
  • 失敗する先生(B)は 別の Google ワークスペースの個人アカウント(A = smalruby.jp の Workspace・Classroom 有効 / B = gmail.com・Workspace 契約なし)。B が A のコースの教師になることは、運用で解決できる話ではなく恒久的にできない状態です。

つまりこれは「今は権限が無いが、同意すれば通る」ではなく「何をしても通らない」ケースです。だからこそ、押させてから失敗を説明するのではなく、押す前に止める必要があります(既定をブロックにしても、正当な人を締め出すリスクは 2. の解除導線で回収できます)。

handleCheckGoogleCourseAccess は googleAccessToken || getCachedClassroomAccessToken() が無いと UNKNOWN を返し、isGoogleShareBlocked は notTeacher のときだけ true を返します。B はトークンを持ち得ないので常に UNKNOWN = ボタンは有効のままで、押して失敗する体験は変わりません(文言が親切になるだけ)。Issue の受け入れ条件「GC 教師ではない共同担当は共有ボタンが無効」が、まさにその人で満たされません。

直してほしいこと

既定を逆にし、事前判定を「ブロックの根拠」ではなく**「ブロックを解除する根拠」**として使ってください。

  1. 連携した先生でなければ既定で disabled。クラスに GC コースが紐付いていて、サインイン中の先生が googleClassroomLinkedBy(既存データはクラス作成者 teacherSub にフォールバック)でないなら、UNKNOWN でもボタンを無効化し、今の「◯◯ 先生に依頼してください」を表示する。
    • 判定はクライアントでメール文字列を突き合わせず、サーバーが真偽値を返す形にしてください(GET /classroom-groups か google-course-access に linkedByMe 等)。メールの大小文字・別名でぶれます。
  2. GC 共同教師の救済は明示的なオプトインにする。ブロック表示の中に「自分も この Google Classroom の先生です(確認する)」のような導線を置き、押されたときだけ requestClassroomAccessToken() → 既存の probe を実行し、teacher なら共有を解禁する。これなら自動では同意ポップアップを出さないという現行方針を保ったまま、正当な GC 共同教師は 1 クリックで従来どおり共有できます。
    • B のようにスコープを与えられないアカウントでは、この確認は失敗で終わります。googlePermissionDenied のハンドリングに乗せて、エラーバナーではなくブロック表示のままにしてください(「確認できませんでした。◯◯ 先生に依頼してください」)。
  3. 連携した先生本人はこれまでどおり(probe 不要・無効化しない)。本人が 404 になるのはコースが削除/アクセス不能なときなので、その経路の文言は「依頼してください」ではなく**「連携し直してください」**系(既存の googleCourseNotFound 相当)のままにしてください。googleNotCourseTeacher を本人に出すと案内が誤ります。

受け入れ条件の追加

  • Google Classroom を一度も使っていない(トークンを持たない)共同担当が、クラス詳細を開いた直後から共有ボタンを押せない
  • その状態で「確認する」を押し、同意が得られない/教師でないときも、エラーバナーではなくブロック表示のまま理由が読める
  • 同じコースの GC 共同教師は「確認する」1 回で共有でき、以後その先生は無効化されない
  • 連携した先生本人は、確認導線を一切経ずに従来どおり共有できる
  • 連携した先生本人が 404 になった場合の文言が「依頼」ではなく「連携し直す」になっている

googleClassroomLinkedBy の記録・reason 分離・文言・テストの土台は今の PR のままで良く、変更は「既定値の反転」と「解除導線の追加」です。

[FYI] 今回の事例はメールドメインが違います(連携した先生 @smalruby.jp / 押した先生 @gmail.com)が、ドメイン一致をゲートの判定条件にしないでください。Workspace の設定次第で外部ドメインの先生をコースに招待できるため、ドメインが違っても正当な GC 共同教師はあり得ます。判定の根拠はあくまで「連携した本人か」+「probe が teacher を返したか」の 2 つに留めてください。

Refs #1175

@takaokouji takaokouji left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#1185 (comment) の指摘事項に対応して。

@takaokouji takaokouji removed the 🙋 HITL 人間の対応待ち(Project HITL=Yes の投影) label Oct 10, 2026
…d it

共有できない先生の多くは「同意すれば通る」のではなく「何をしても通らない」
(別ワークスペースの個人アカウントは、そのコースの教師になれない)。事前判定を
「ブロックの根拠」ではなく「ブロックを解除する根拠」として使うよう既定を反転した。

- 連携した先生(googleClassroomLinkedBy。既存データは teacherSub)以外は既定で
  共有ボタンを disabled にする。判定はサーバーが真偽値 linkedByMe を返す形にし、
  クライアントでメール文字列を突き合わせない(大小文字・別名でぶれるため)
- GC 共同教師の救済は明示的なオプトイン: ブロック表示の中の
  classroom-post-assignment-verify を押したときだけ同意を求めて判定し、teacher なら
  共有を解禁する。クラスを開いただけでは同意ポップアップを出さない方針は維持
- 同意が得られない / notTeacher のときはエラーバナーを出さず、ブロック表示のまま
  「確認できませんでした。◯◯ 先生に依頼してください」に差し替える
- 連携した先生本人は判定を経ずに共有でき、その 404 は googleNotCourseTeacher では
  なく googleCourseNotFound(連携し直す案内)にする。自分に依頼させない
- 文言はドメイン一致を判定条件にしない(外部ドメインの GC 共同教師はあり得る)

Refs #1184 #1175

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@smalruby3-editor-bot smalruby3-editor-bot Bot added the deploy-stg この PR の infra 変更を stg にデプロイする (#1162) label Oct 10, 2026
@smalruby3-editor-bot

Copy link
Copy Markdown
Author

指摘 7 件(Must 1 / Question 2 / FYI 4 相当): 対応 4 件 / 対応不要 3 件。77a1ae90a2 で push しました。

対応した指摘

[Must] 事前判定が「すでにトークンを持っている」ときしか走らず、この Issue の先生を素通りする → 既定を反転しました。

  1. 連携した先生以外は既定で disabled(isGoogleShareBlocked)。判定はサーバーの真偽値で、クライアントでメール文字列を突き合わせません:
    • GET /classroom-groups → googleClassroomLinkedByMe
    • GET …/google-course-access → linkedByMe
    • 根拠は googleClassroomLinkedBy(既存データはクラス作成者 teacherSub にフォールバック)
    • トークンを持たない共同担当は unknown のまま=クラス詳細を開いた直後からボタンが押せない
  2. GC 共同教師の救済は明示的なオプトイン。ブロック表示の中に「自分もこの Google Classroom のクラスの先生です(確認する)」(classroom-post-assignment-verify)を置き、押されたときだけ requestClassroomAccessToken() → 既存の probe を実行します。teacher なら共有を解禁。クラスを開いただけで同意ポップアップを出さない方針は維持しています。
    • 同意が得られない / notTeacher のときはエラーバナーを出さず、ブロック表示のまま「…先生であることを確認できませんでした。共有は ◯◯ 先生に依頼してください。」に差し替え(googleShare.verifyFailed*)。notTeacher と確定したら確認ボタンは出し続けません(同じ答えが返るだけなので)。
  3. 連携した先生本人はこれまでどおり。probe 不要・無効化なし。さらに本人の 404 はサーバーが postCourseWorkAsLinker として扱い、googleNotCourseTeacher ではなく googleCourseNotFound にマップします。文言も「クラスが削除されたか、アクセスできなくなっている可能性があります。クラス管理の画面で Google Classroom を連携し直してください。」に変更しました(自分に依頼させない)。
  4. [FYI] ドメイン一致をゲートにしない → していません。判定は「連携した本人か」+「probe が teacher か」の 2 つだけです。

これにより、前回わたしが保留した [Question]「コース削除時も notTeacher と断定して自分に依頼しろと言われる」も解消しました(連携者本人の 404 は「連携し直す」側に分かれます)。

受け入れ条件

  • トークンを持たない共同担当が、クラス詳細を開いた直後から共有ボタンを押せない
  • 「確認する」で同意が得られない/教師でないときも、エラーバナーではなくブロック表示のまま理由が読める
  • 同じコースの GC 共同教師は「確認する」1 回で共有でき、以後その先生は無効化されない(status=teacher が解除根拠)
  • 連携した先生本人は確認導線を経ずに従来どおり共有できる
  • 連携した先生本人の 404 の文言が「依頼」ではなく「連携し直す」

対応不要と判断した指摘

  • [FYI] disabled なボタンの title は多くのブラウザで出ない — 情報は classroom-post-assignment-blocked の本文に出ているため、現状維持。
  • [FYI] 選択中クラスの GC 連携を張り替えても事前判定が追従しない — 既定がブロックになったことで影響が変わりました。張り替えた本人は group の googleClassroomLinkedByMe が true になる(更新レスポンス/一覧の再取得で入る)ため、古い probe 結果に関わらずボタンは有効です。
  • [FYI] linkedByEmail の出どころが 2 つある — google-course-access は本人のとき null を返し、クライアントは group 側の値にフォールバックする構造のまま。今回ブロック対象から本人が外れたので、本人に自分のアドレスが出る経路自体がなくなりました。

テストと確認

  • 新規/更新: classroom-google-share.test.js(既定ブロック・連携者は常に許可・probe による解除・確認導線の出し分け)、teacher-class-detail.test.jsx(開いた直後から disabled / 確認ボタンの発火・失敗表示・実行中表示 / pre-v2 の proof-only 維持)、handler-google-course-access.test.ts(isGoogleClassroomLinker と postCourseWorkAsLinker の 404 マッピング)
  • classroom lambda 23 suites 351 tests pass、tsc --noEmit clean、scratch-gui 関連 53 tests pass、npm run lint(eslint + prettier)clean
  • サーバー変更は後方互換(任意フィールド追加のみ)なので deploy-stg ラベルを付けました。stg 反映後に UI の DoD をお願いします(googleClassroomLinkedByMe が無い API に対しては、連携者本人も一旦ブロック表示になり「確認する」で解除される挙動になります)。
  • docs: docs/classroom/architecture.md / ui-ux.md / testing.md / source-code.md、.claude/rules/scratch-gui/e2e-test.md を更新しました。

@github-actions

Copy link
Copy Markdown

🚀 stg にデプロイしました: smalruby-classroom

項目 値
commit 77a1ae90a283343effa2316285c5858aa8e80a7d
branch topic/autopilot-1184
run https://github.com/smalruby/smalruby3-editor/actions/runs/38034906473

stg は共有環境です。この SHA が載っている前提で 動作確認してください。
別の PR があとからデプロイすると上書きされます。

deploy-stg ラベルはこの run の完了時に自動で外れます。もう一度 stg に載せたいときはラベルを付け直してください。

@github-actions github-actions Bot removed the deploy-stg この PR の infra 変更を stg にデプロイする (#1162) label Oct 10, 2026

This branch was successfully deployed

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

Labels

🤖 autopilot autopilot が処理対象にした Issue/PR(daemon が付与)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(classroom): 連携した先生以外が Google Classroom に共有できてしまい API エラーになる

1 participant