From 7f55471c4c5644351b8792a85b4d71da790dde43 Mon Sep 17 00:00:00 2001 From: "smalruby3-editor-bot[bot]" <297607354+smalruby3-editor-bot[bot]@users.noreply.github.com> Date: Wed, 1 Jul 2026 01:20:27 +0000 Subject: [PATCH 1/2] feat(tutorial): apply setup.rubyVersion in deck-setup foundation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit deck 起動時の setup 基盤 (applyDeckSetup) は tab / rubyMode / extensions を 既に適用していたが、Issue で宣言された setup プロパティの rubyVersion だけ 未対応 (ドキュメントマーカーのみ) だった。これを実装して設計どおり完成させる。 - setup.rubyVersion (1|2 の数値、'1'|'2' の文字列どちらも受理) を正規化 - 設定メニュー / URL ローダーと同じく setRubyVersion dispatch + persistRubyVersion - 不正値・省略時は無視 (現状維持)。冪等・後方互換を維持 - 単体テスト 3 ケース追加 (数値/文字列/不正値)。全 13 ケース pass Closes #849 Co-Authored-By: Claude Opus 4.8 (1M context) --- packages/scratch-gui/src/lib/deck-setup.js | 24 ++++++++++-- .../test/unit/lib/deck-setup.test.js | 38 +++++++++++++++++++ 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/packages/scratch-gui/src/lib/deck-setup.js b/packages/scratch-gui/src/lib/deck-setup.js index 8b2b39e2607..c877e327bb5 100644 --- a/packages/scratch-gui/src/lib/deck-setup.js +++ b/packages/scratch-gui/src/lib/deck-setup.js @@ -7,6 +7,9 @@ import { RUBY_TAB_INDEX, SOUNDS_TAB_INDEX, } from '../reducers/editor-tab'; +import { setRubyVersion } from '../reducers/settings'; +import { VERSION_1, VERSION_2 } from './settings/ruby-version'; +import { persistRubyVersion } from './settings/ruby-version/persistence'; const FURIGANA_ENABLED_KEY = 'smalruby:furiganaEnabled'; @@ -20,6 +23,14 @@ const TAB_INDEX_MAP = { const VALID_RUBY_MODES = new Set(['ruby', 'furigana', 'dncl']); +// setup.rubyVersion may be given as a number (1 | 2) per the design doc, or +// as the string form ('1' | '2') used internally by the settings reducer. +// Normalize to the internal string form, or null if unsupported. +const normalizeRubyVersion = (value) => { + const asString = String(value); + return asString === VERSION_1 || asString === VERSION_2 ? asString : null; +}; + /** * Apply tutorial deck setup: switch editor tab, set Ruby mode, load * required extensions. Each operation is idempotent — calling this with the @@ -32,6 +43,7 @@ const VALID_RUBY_MODES = new Set(['ruby', 'furigana', 'dncl']); * tab: 'ruby', // 'code' | 'costumes' | 'sounds' | 'ruby' * rubyMode: 'dncl', // 'ruby' | 'furigana' | 'dncl' * extensions: ['pen', 'microbitMore'], + * rubyVersion: 2, // 1 | 2 (omit to keep current) * }, * // ... * } @@ -88,8 +100,12 @@ export const applyDeckSetup = async (setup, dispatch, vm) => { } } - // 4. Ruby version is intentionally not handled in Phase 2 foundation — - // no deck currently needs it. The hook is left here as a documentation - // marker for future expansion (would dispatch settings reducer action). - // if (setup.rubyVersion === 1 || setup.rubyVersion === 2) { /* TBD */ } + // 4. Ruby version (optional). Mirrors how the settings menu / URL loader + // switch versions: dispatch the reducer action and persist the choice so + // it survives a remount. Unsupported values are ignored. + const rubyVersion = normalizeRubyVersion(setup.rubyVersion); + if (rubyVersion) { + dispatch(setRubyVersion(rubyVersion)); + persistRubyVersion(rubyVersion); + } }; diff --git a/packages/scratch-gui/test/unit/lib/deck-setup.test.js b/packages/scratch-gui/test/unit/lib/deck-setup.test.js index 16de0cf1150..73fafb7ad59 100644 --- a/packages/scratch-gui/test/unit/lib/deck-setup.test.js +++ b/packages/scratch-gui/test/unit/lib/deck-setup.test.js @@ -2,6 +2,8 @@ import { applyDeckSetup } from '../../../src/lib/deck-setup'; import { SET_DNCL_MODE } from '../../../src/reducers/dncl-mode'; const ACTIVATE_TAB = 'scratch-gui/navigation/ACTIVATE_TAB'; +const SET_RUBY_VERSION = 'scratch-gui/settings/SET_RUBY_VERSION'; +const RUBY_VERSION_KEY = 'smalruby:rubyVersion'; const BLOCKS_TAB_INDEX = 0; const COSTUMES_TAB_INDEX = 1; @@ -105,6 +107,42 @@ describe('applyDeckSetup', () => { expect(dispatch.calls.filter((a) => a.type === SET_DNCL_MODE)).toEqual([]); }); + test('rubyVersion (number) dispatches setRubyVersion and persists to localStorage', async () => { + for (const [input, expected] of [ + [1, '1'], + [2, '2'], + ]) { + const dispatch = makeDispatch(); + window.localStorage.clear(); + await applyDeckSetup({ rubyVersion: input }, dispatch, makeVM()); + expect(dispatch.calls).toContainEqual({ + type: SET_RUBY_VERSION, + rubyVersion: expected, + }); + expect(window.localStorage.getItem(RUBY_VERSION_KEY)).toBe(expected); + } + }); + + test('rubyVersion (string) is accepted as well', async () => { + const dispatch = makeDispatch(); + await applyDeckSetup({ rubyVersion: '1' }, dispatch, makeVM()); + expect(dispatch.calls).toContainEqual({ + type: SET_RUBY_VERSION, + rubyVersion: '1', + }); + expect(window.localStorage.getItem(RUBY_VERSION_KEY)).toBe('1'); + }); + + test('invalid or omitted rubyVersion is ignored', async () => { + for (const rubyVersion of [undefined, 3, 0, '9', 'bogus', null]) { + const dispatch = makeDispatch(); + window.localStorage.clear(); + await applyDeckSetup({ rubyVersion }, dispatch, makeVM()); + expect(dispatch.calls.filter((a) => a.type === SET_RUBY_VERSION)).toEqual([]); + expect(window.localStorage.getItem(RUBY_VERSION_KEY)).toBeNull(); + } + }); + test('loads required extensions sequentially', async () => { const dispatch = makeDispatch(); const vm = makeVM(); From a0ff24e751c7b6232c9b3229d772c0709efe67f7 Mon Sep 17 00:00:00 2001 From: "smalruby3-editor-bot[bot]" <297607354+smalruby3-editor-bot[bot]@users.noreply.github.com> Date: Wed, 1 Jul 2026 01:24:07 +0000 Subject: [PATCH 2/2] docs(tutorial): mark setup.rubyVersion as implemented in progress --- docs/tutorial/progress.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/tutorial/progress.md b/docs/tutorial/progress.md index 97e61e2857a..a3f8c4553f8 100644 --- a/docs/tutorial/progress.md +++ b/docs/tutorial/progress.md @@ -53,12 +53,12 @@ ### 基盤 (Phase 2 前半): `setup` プロパティ - [x] `src/lib/deck-setup.js` 新規追加 (`applyDeckSetup` ヘルパー) -- [x] deck 定義の type 拡張 (`{ tab, rubyMode, extensions, rubyVersion }`) — `rubyVersion` は将来用フックのみ +- [x] deck 定義の type 拡張 (`{ tab, rubyMode, extensions, rubyVersion }`) — `rubyVersion` も適用 (`dispatch(setRubyVersion)` + `persistRubyVersion`、不正値は無視) - [x] `tips-library.jsx` で deck 起動時に setup を適用 (vm prop 接続含む) - [x] `activateTab` / `setDnclMode` / `vm.extensionManager.loadExtensionURL` の冪等な呼び出し - [x] ロード失敗時のグレースフルデグレード (`console.warn` のみ、deck は開く) - [x] ふりがなフラグも考慮した rubyMode の動作 (`smalruby:furiganaEnabled` localStorage の同期) -- [x] `test/unit/lib/deck-setup.test.js` で 10 ケースの単体テスト (全 pass) +- [x] `test/unit/lib/deck-setup.test.js` で 13 ケースの単体テスト (全 pass) ### Phase 3 着手前に必要 (外部要因)