feat: upstream merge 2026-05 (v13.7.2 — 284 commits, scratch-blocks v2)#630
Merged
Conversation
chore: Update dependency of scratch-storage to v6.2.0
…king-of-packages feat: add symlinks:false to webpack config
Publish tested builds
Apparently, `-F` (or `-f`) causes `gh api` to switch to a `POST`, which then causes the request to fail with a 404. Forcing `GET` makes it work.
ConfirmationPrompt の cancel/confirm ボタンを配列で render する際、React の unique key prop が無いため warning が発生していた。 StageHeaderComponent → ConfirmationPrompt を経由して console error として 記録されていたが、key を付与することで解消。 仕様変更ではなく React のベストプラクティス対応。
Smalruby 固有コードが Blockly の trailing-underscore (private) フィールドや internal API に新規に依存していないかを CI で自動検出するユニットテストを 追加する。 upstream merge のたびに private フィールドはリネーム/削除されうるので、 本テストで早期検知して公開 API への migrate を促す。 実装方針: - src/ 配下を再帰スキャンし、Blockly オブジェクト名 (workspace, flyout, toolbox, ScratchBlocks, Blockly, ...) からの `<obj>.<name>_` パターンを 正規表現で検出 - 既知で公開 API が無い箇所は ALLOWLIST に理由付きで登録 - ALLOWLIST の stale エントリも検出する 2nd test を追加 現在の ALLOWLIST: - src/lib/blocks-gesture-recovery.js: `workspace?.currentGesture_` (drag 中状態取得の公開 API が v12 にも無い) - src/containers/blocks.jsx: `flyout.svgGroup_` (palette toggle 用、flyout.hide() 単独では再表示されてしまう) - src/containers/blocks.jsx: `ScratchBlocks.FieldColourSlider.activateEyedropper_` (scratch-blocks の eyedropper 拡張ポイント、upstream 利用) - src/lib/blocks.js: `ScratchBlocks.FieldNote.playNote_` (scratch-blocks の音符再生コールバック拡張ポイント、upstream 利用)
upstream の PlayButton container では getDerivedStateFromProps が インスタンスメソッドとして定義されているが、React 16.3+ では static でないと 完全に無視され、開発モードで warning が発生する。 dev mode (Smalruby ローカル開発) では PlayButton を含む音ライブラリモーダル を開いた際に warning が出ていた。production build では DEV warning が strip されるため scratch.mit.edu では見えなかった。 修正後は touchStarted の派生 state が正しく React によって反映される (これまでは無視されていたため意図せず handleMouseEnter/Leave で再生が 止まらない潜在バグもあった)。
PlayButton で getDerivedStateFromProps が static でないバグが production build (DEV warning strip) のため scratch.mit.edu で見えず、長期間放置されて いた。同種のバグ (getDerivedStateFromError 等) を lint で systematic に検出 できるよう react/no-typos ルールを error として有効化する。 verification: 一時的に static を外して lint 実行 → "Lifecycle method should be static: getDerivedStateFromProps" で正しく検出されることを確認。 併せて、これまで markers ドキュメントに記載漏れだった eslint.config.mjs の prettier integration マーカーも追記。
…ks v2 Ruby → blocks 変換で生成される `@ruby:method:*` などの内部メタデータ コメントが、scratch-blocks v2 (Blockly v12) では minimize 表示されず、 全コメントが同じ workspace 座標 (200, 0) にスタックして workspace の 別の場所に浮いていた。 これは PR #271 の v1 ダウングレードで対応していなかった v2 仕様変更: 1. v1 の `comment.setMinimized(true)` API は v2 で廃止され、ブロック付随 コメントは block の **comment icon** として実装された (`block.getIcons()` から `IconType.comment` を取得して操作) 2. icon の `setBubbleVisible(false)` で minimize できるが、Events.disable 中に呼ぶと no-op、また onWorkspaceUpdate 直後の同期呼び出しは Blockly の post-load render に上書きされる → 100ms 遅延 + block ID から再取得 する必要がある 3. v2 は collapsed (minimized) 状態でも comment.x/y を保持するため、 converter が常に (200, 0) を割り当てると複数の bubble が完全に重なる → block ごとに `setBubbleLocation(blockX + 220, blockY)` で再配置 加えて scratch-vm 側で `block.x` が undefined の topLevel block の XML 出力が `x="undefined"` になり、scratch-blocks v2 がこれを 0 として読み込み move event を発火、`fromRuby` 検出 (`typeof topBlock.x === 'undefined'`) が効かなくなっていた問題も修正。`Number.isFinite` で実値があるときだけ 属性を出力するようガードを追加。 修正後: - `puts "hello"` 1 行 → 該当 say block 横に minimize されたバッジ表示 - `puts "a"` + `puts "b"` 複数 → 各 block にバッジが正しく分離して表示 - `def/return` を含むコードでも `@ruby:return` マーカー付き block は 既存の getTopComments 経路で処理され従来通り動作
前回の修正で各ブロック直下に bubble を配置したが、ネストされた input ブロック (例: \`puts a\` の \`a\` 変数式) ではコメントが内側ブロックの x を 基準に置かれ、トップレベルブロックの comment と左端が揃わなかった。 \`getParent()\` を辿って top-level (statement chain の根) の x を取得し、 スクリプト内のすべての \`@ruby:*\` comment がその左側 (rootX - 220) で 左端を共有するよう修正。
\`@ruby:class:...\` のような workspace-level comment (blockId=null) は scratch-blocks v2 で WorkspaceComment として保持され、v1 の \`comment.setMinimized(true)\` API は廃止された (\`setCollapsed(true)\` に リネーム)。前回の修正で setMinimized 呼び出しを削除した結果、クラスコメント が大きな黄色いバブルとして expanded 状態で表示されていた。 修正: - workspace.getTopComments() の \`@ruby:*\` コメントに対して \`setCollapsed(true)\` を呼び出して collapse 表示 - 位置を first top block の x - 220 に揃える (block-attached コメントと 同じ左端アライメント) これで \`class Sprite1 < ::Smalruby3::Sprite ... end\` のような Ruby から 変換した script の \`@ruby:class:\` メタデータコメントが、他の \`@ruby:*\` コメントと同じく左側に小さなバッジとして表示される。
\`@ruby:class:...\` の WorkspaceComment は前回の修正で setCollapsed(true) を呼ぶようにしたが、これは Events.disable() 中なので no-op、また onWorkspaceUpdate 直後の同期呼び出しは scratch-blocks v2 の post-load render に上書きされる (block 付き comment icon と同じパターン)。 修正: - 同期ブロックでは comment ID だけ集める - finally の Events.enable() 後 setTimeout 100ms で再 fetch して setCollapsed(true) を実行 block-attached comment と完全に同じ deferred apply パターンに統一。 これで class コメントも他の \`@ruby:*\` コメントと同様に小さなバッジ として表示される。
…ispose error
Ruby converted scripts that introduce list variables (e.g.
\`a = [1, 2, 3]; a.each do |i| puts i end\`) crashed the editor with
infinite "Cannot read properties of undefined (reading '2')" errors at
\`disposeItem\`/\`clearOldBlocks\` inside scratch-blocks v2's flyout
recycling path:
TypeError: Cannot read properties of undefined (reading '2')
at u (main.mjs:1:1)
at DS.dispose (main.mjs:1:1)
at pA.disposeItem (main.mjs:1:1)
at DE.clearOldBlocks (main.mjs:1:1)
at DE.show (main.mjs:1:1)
at gE.forceRerender (main.mjs:1:1)
at Blocks.updateToolbox (blocks.jsx:439)
Root cause: when forceRerender() throws, \`_renderedToolboxXML\` is
never assigned, so the next componentDidUpdate sees toolboxXML still
diverging from the rendered state and calls requestToolboxUpdate()
again, which re-throws, ad infinitum (the console flooded with the
same trace until the page was unusable).
修正:
- updateToolbox の \`_renderedToolboxXML = props.toolboxXML\` を
forceRerender の **前** に移動 (例外で skip されないように)
- forceRerender を try/catch + setRecyclingEnabled(false) で囲む
(recycling 経路を回避し、v2 内部の dispose クラッシュを抑止)
- 失敗時は \`log.error\` で 1 回だけ通知し、エディタは継続動作
これで a.each など配列リテラルを含む Ruby が無限エラーを起こさず、
ブロックも正しく描画されるようになる (動作確認済み)。
6 tasks
…` literals Issue #634 (under feat/upstream-merge-2026-05). Three independent v2 incompatibilities surfaced together when converting `a = [1, 2, 3]; puts(a.max)` and similar Ruby code: 1. **`ScratchBlocks.FieldVerticalSeparator` removed in v2.** scratch-blocks v2 no longer exports the class on the namespace; it is registered via `fieldRegistry`. The old `new ScratchBlocks.FieldVerticalSeparator()` call in `defineDynamicBlock.createAllInputs` threw mid-flight in `domToMutation`, which made the surrounding XML parser drop the rest of the block's `<next>` chain — every Ruby-converted script that touched an extension block with an icon ended up as detached top-level blocks. Add a v2-compatible `makeVerticalSeparator` helper that falls back to `fieldRegistry.fromJson({type: 'field_vertical_separator'})`. 2. **Eager scalar created by `_onVasgn`.** `assignments.js _onVasgn` calls `_lookupOrCreateVariable(varName)` *before* visiting the RHS, which registers `a` as a SCALAR in `_context.localVariables`. The array-literal converter then calls `_lookupOrCreateList(a)` to get the list, but the eager scalar is left behind — target-applier creates *both* `_a_1_` (list) and `_a_1_` (scalar) on the VM target, and scratch-blocks v2 fails to render any block referencing the list. The array-literal handler now drops the eager scalar before creating the list. 3. **Cross-store re-creation at top level.** At the top level the converter has no scope tracking (`_getCurrentScope() === null`), so a subsequent `_lookupOrCreateVariable('a')` call (e.g. from `visitLocalVariableReadNode` for `a.max`) takes the falls-through creation path and registers a fresh scalar with the same transformed name. Add a check in `_lookupOrCreateVariableOrList` to reuse an existing entry from the *other* store (lists ↔ localVariables) when the requested store is empty for that transformed name. Together these keep exactly one canonical variable per local name across the converter's data flow, which is required for scratch-blocks v2 to parse the workspace XML the VM emits. Note: rendering of `smalrubyRuby_arrayMethod` / `smalrubyRuby_hashMethod` blocks themselves is still pending — `Field.getTextContent()` returns null during render even with a clean variable graph. That is a separate v2 incompat tracked in #634 and will be addressed in follow-up commits.
…h on v2 Issue #634 — final fix. Smalruby's `defineDynamicBlock.updateBlockDisplay` followed a v1-style pattern that toggled `block.rendered = false` around `appendField` and re-issued `block.initSvg()` + `block.render()` at the end: const wasRendered = block.rendered; block.rendered = false; ... createAllInputs(block, ...); // → appendField(new FieldDropdown(...)) ... block.rendered = wasRendered; if (wasRendered) { block.initSvg(); block.render(); } In Blockly v12 (the runtime under scratch-blocks v2.1.19) this dance silently drops field initialisation: Input.appendField(field, name) { field.setSourceBlock(this.sourceBlock); this.sourceBlock.initialized && this.initField(field); this.sourceBlock.rendered && this.sourceBlock.queueRender(); } Input.initField(field) { this.sourceBlock.rendered ? field.init() : field.initModel(); } For a block constructed via `domToMutation`: * `block.initialized` is `false` until `initSvg()` runs the one-shot guard. * `block.rendered` is `true` (BlockSvg constructor default). Smalruby was forcing `rendered = false` during `appendField`, so: * `appendField` saw `initialized=false` → skipped `initField` entirely → field's SVG `<text>` element never created. * The follow-up `block.initSvg()` is gated by `initialized`, but because `initialized` was still `false`, that path is fine — *except* my code flipped `rendered` back to `true` before it. With `rendered=true`, `initSvg`'s field-init would run `field.init()` (which creates the SVG text). Either path *should* work. * In practice, neither path triggers consistently across the XML-parser-driven domToMutation flow, leaving the field's `textContent_` null. The next `block.render()` invokes the block renderer, which calls `Field.getSize` → `Field.render_` → `Field.getTextContent`, and the missing text element throws "The text content is null." Stack: at Field.getTextContent at Field.render_ at Field.getSize at new BlockRenderRow at BlockRenderer.createRows_ at BlockRenderer.measure at Block.render The error aborts the parser mid-tree, drops the rest of the block's `<next>` chain, and leaves Smalruby's flyout/workspace unable to render any script that references `smalrubyRuby_arrayMethod`, `smalrubyRuby_hashMethod`, or `smalrubyRuby_stringMethod`. Fix: stop toggling `block.rendered`. Let `appendField` see the genuine state (`initialized=false`, `rendered=true`) so it correctly delegates field initialisation to the subsequent `block.initSvg()` call we make at the end. `block.initSvg()` walks `inputList` and calls `field.init()` for every field, which creates each field's SVG `<text>` element. We also keep `block.render()` to re-measure the block with its new inputs. This mirrors what scratch-blocks v2's own `procedures.ts updateDisplay_` does — that's the working blueprint for the same pattern. After the fix: * `puts(a.max)` round-trips correctly (`a = [1, 2, 3]` block chain followed by `Array (a) . max` connected to `戻り値 と 1 秒言う`). * `Field.getTextContent` no longer throws. * Toolbox refresh no longer fails. Verified manually with Playwright on `?tab=ruby&ruby_version=2`.
7 tasks
… = [...]` literals" This reverts commit cf7dc65.
… on v2
Three coordinated fixes for the array/hash method flyout crash on
scratch-blocks v2:
1. define-dynamic-block.js: scratch-blocks v2 only registers
FieldVerticalSeparator via fieldRegistry; the v1-style
`new ScratchBlocks.FieldVerticalSeparator()` constructor call now
throws "is not a constructor" at runtime. Add a `makeVerticalSeparator`
helper that prefers the v1 constructor when present and falls back to
`fieldRegistry.fromJson({type: 'field_vertical_separator'})` for v2.
2. target-applier.js: the converter eagerly creates a SCALAR entry in
`_context.localVariables` for `t = [...]` before visiting the RHS.
The array-literal handler then creates a LIST entry in `_context.lists`
under the same transformed name. Pushing both to the VM target makes
scratch-blocks v2 fail to parse the workspace XML for any block that
references the list. Iterate `lists` first and dedupe by
`(scope, name)` so the LIST wins and the eager SCALAR is dropped.
3. smalruby-ruby.js (Ruby generator): the bang-method generator
(`sort!`, `reverse!`) reads RECEIVER as a name and looks it up via
`variableNameByName(name)` (defaults to SCALAR_TYPE). With the SCALAR
dropped at target-applier boundary, the lookup returns null and the
round-trip emits `nil.sort!`. Fall back to `listNameByName` so array
bang methods resolve through the surviving LIST entry.
Closes #634
…-flyout-crash fix: resolve scratch-blocks v2 render crash for arrayMethod/hashMethod (#634)
`blocks-screenshot.js` was already migrated to the v2 method-style API (`workspace.getCanvas()` / `workspace.getBubbleCanvas()`), but the unit test mock still exposed the v1 direct-property surface (`svgBlockCanvas_` / `svgBubbleCanvas_`). 9 tests failed with `TypeError: workspace.getBubbleCanvas is not a function` on feat/upstream-merge-2026-05 CI. Update `makeMockWorkspace` to return jest.fn-wrapped getters, and replace the one inline `svgBubbleCanvas_` access with the equivalent method call. Closes #636
…th comments Three coordinated fixes that together stop `toBlob()` from throwing `SecurityError: Tainted canvases may not be exported.` when the workspace contains the `@ruby:*` comments produced by the Ruby→Blocks converter: 1. Strip `<foreignObject>` from the BLOCK canvas clone, not only the bubble canvas. In scratch-blocks v2 (Blockly v12), block-attached comments are nested inside the block canvas and contain a `<foreignObject>` for the editing UI. Once the export SVG is loaded via `blob://`, those foreign objects taint the canvas. 2. Strip external `url(...)` references from injected `<style>` blocks via a new `stripExternalCssUrls` helper. The blocky stylesheet that carries the `blocklyText` color rules also contains `url(./static/blocks-media/.../sprites.png)` and friends — they're only used for cursors and toolbox sprite chrome, but the relative refs taint the rasteriser. 3. Drop hidden `<image>` elements that have an empty `href` (used as placeholders inside dynamic-block dropdowns, `display:none`). An empty href falls back to the document URL and tainted canvas under blob origin. Adds unit tests for each piece and exports `stripExternalCssUrls` for isolated testing. Verified live: project with `puts(a.max)` / `t.sort!` etc. that emit multiple `@ruby:*` comments now exports a PNG without errors.
…t its joined string
`executeArrayMethod` (non-bang) reconstructed `items` from
`String(args.RECEIVER).split(' ')`. Scratch's `data_listcontents`
joins single-character items with NO separator (e.g. `[3,1,4,1,5,9,2,6]`
becomes `"31415926"`), so the split returned a single token and every
method (max/min/first/last/sort/join/reverse) returned that joined
string back rather than the computed value.
Walk the block tree from `util.thread.peekStack()` to find the
RECEIVER input block; if it's a `data_listcontents`, look up the list
directly via `target.lookupVariableById` / `lookupVariableByNameAndType`
and operate on `list.value`. Falls back to the space-split heuristic
for non-list receivers (e.g. literal strings).
Verified live: `arr = [3,1,4,1,5,9,2,6]` now reports
- arr.max = "9", arr.min = "1", arr.first = "3", arr.last = "6"
- arr.sort.join(",") = "1,1,2,3,4,5,6,9"
- arr.sort! mutates the list to [1,1,2,3,4,5,6,9]
- arr.join("-") = "1-1-2-3-4-5-6-9"
…canvas test(blocks-screenshot): align mock workspace with scratch-blocks v2 API
…-blocks v2 scratch-blocks v2's `ScratchZoomControls` uses MARGIN_VERTICAL = MARGIN_HORIZONTAL = 20 (vs v1's 12) inside the workspace's view metrics (which exclude the 11px scratch-blocks scrollbar). The Code tab camera button was hard-coded at right=22/bottom=154 — values tuned for v1 — so it drifted off the v2 zoom column on desktop and shifted further on mobile / window resize because scrollbar width is platform dependent. The Ruby tab's zoom column was at right=22/bottom=22, which sat 11px inboard of the Code tab's actual on-screen position, producing a visible jump when switching tabs. Fixes: - `BlocksScreenshotButton` now measures the live `.blocklyZoomIn` position via `useLayoutEffect` + `ResizeObserver` and pins the camera button 8px above its top edge, with right edges flush. Survives scrollbar variance, mobile orientation, and any v2 zoom-controls reflow. CSS keeps right=20/bottom=152 as a one-frame fallback that matches the v2 layout (MARGIN(20) + ZOOM_HEIGHT(124) + GAP(8)). - `ruby-tab.css` `.zoomControlsWrapper` moves to right=31/bottom=31 (= MARGIN(20) + scratch-blocks scrollbar(11)) so the Ruby tab column lands at the same screen X/Y as the Code tab's zoom controls. Verified with Playwright at desktop and 800/812-wide viewports — camera buttons match within 1px. Removes orphan `.downloadButton` / `.downloadIcon` / `.downloadWrapper` rules from ruby-tab.css; ruby-tab.jsx hasn't used them since the camera moved into `zoomControlsWrapper`.
…osition fix(zoom-ui): align screenshot button + Ruby zoom column with scratch-blocks v2
github-actions Bot
pushed a commit
that referenced
this pull request
May 5, 2026
…-merge-2026-05 feat: upstream merge 2026-05 (v13.7.2 — 284 commits, scratch-blocks v2)
takaokouji
added a commit
that referenced
this pull request
May 13, 2026
…tream merge The Shimaraby and Shimacat sprite/costume entries were silently overwritten during upstream merges (most recently in PR #630, and also in the v13.7.2 merge before that). The asset PNGs in static/smalruby-assets/ remained but the library entries pointing to them were gone, so the sprites disappeared from the editor. - Re-add Shimaraby and Shimacat sprite entries to sprites.json (between Shark 2 and Shirt) - Re-add Shimaraby-a/b and Shimacat-a/b costume entries to costumes.json - Add smalruby-original-sprites.test.js as a regression guard so that future upstream merges fail loudly if these entries are wiped again Refs #688 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
takaokouji
added a commit
that referenced
this pull request
May 13, 2026
…sprites Adds a section to phase2-conflicts.md warning that sprites.json / costumes.json upstream changes do not produce git conflicts but can silently wipe Smalruby-original entries (Shimaraby, Shimacat). Includes the verification commands and the trademark-sprite checklist that must be run after every upstream merge. This is the recurrence-prevention measure for the issue caught in #688 where two consecutive upstream merges (#630 and the v13.7.2 merge before it) wiped Shimaraby/Shimacat without anyone noticing. Refs #688 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Merged 284 upstream commits from
scratchfoundation/scratch-editorv13.7.2 (the production release currently deployed at scratch.mit.edu).Upstream Commit Range:
42ea882750(previous merge) →352a334b4a(v13.7.2)Production deploy: scratch-www
3572d5ad8a(2026-04-30)Major Upstream Changes
scratch-blocksv1.3.0 → v2.1.19 (Blockly v12-based). The v2.0.3 argument-reporter drag bug (scratchfoundation/scratch-blocks#3450) is fixed and the previous Smalruby downgrade (PR fix: downgrade scratch-blocks from v2.0.3 to v1.3.0 to restore custom block argument dragging #271) is reverted.react-redux 8.1.3direct-dep,eslint-config-scratch 14.x,scratch-l10n 6.1.72,scratch-render-fonts, etc.LegacyBackpackStorageclass consolidates the backpack web/storage flow.scratch-media-lib-scriptsworkspace package.npm testnow runsplaywrightin addition to jest unit + integration.Conflict Resolution Strategy
postMergeReverts guidance was 方針A (accept upstream) because the original argument-drag regression is fixed in v2.1.19. All
ScratchBlocks v1 → v2API call sites were rewritten:ScratchBlocks.Xml.textToDomScratchBlocks.utils.xml.textToDomScratchBlocks.Xml.domToTextScratchBlocks.utils.xml.domToTextScratchBlocks.Xml.clearWorkspaceAndLoadFromXmlScratchBlocks.clearWorkspaceAndLoadFromXmlScratchBlocks.Variables.createVariableScratchBlocks.ScratchVariables.createVariableScratchBlocks.Procedures.createProcedureDefCallback_ScratchBlocks.ScratchProcedures.createProcedureDefCallbackScratchBlocks.Procedures.externalProcedureDefCallbackScratchBlocks.ScratchProcedures.externalProcedureDefCallbackScratchBlocks.statusButtonCallbackScratchBlocks.StatusIndicatorLabel.statusButtonCallbackScratchBlocks.prompt = fnScratchBlocks.dialog.setPrompt(fn)workspace.reportValueScratchBlocks.reportValueworkspace.toolbox_.getSelectedCategoryId()workspace.getToolbox().getSelectedItem().getName()inject({ colours })inject({ theme: new ScratchBlocks.Theme(...) })import Blockly from 'scratch-blocks'import * as Blockly from 'scratch-blocks'color-mode/{default,dark,high-contrast,blockHelpers}.jswere re-synced from upstream (key renameprimary→colourPrimaryetc.).lib/blocks.jswas replaced wholesale with upstream's v2 version, with only the Smalruby gesture-recovery import / call re-applied.Smalruby Markers Added (upstream files)
legacy-backpack-storage.ts—localStoragehost support (3 markers aroundgetBackpackContents/saveBackpackObject/deleteBackpackObjectcalls and thegetBackpackAssetURLhelper). The mesh v1 → v2 backpack auto-migration incontainers/backpack.jsxkeeps working unchanged.stage-header/stage-header.jsx— re-applied the 3-button toggle (small / middle / large) used by issue bug: iOS Safari など狭幅画面でスプライト/コスチューム追加が画面外になり操作不能 (responsive 対応) #572 Phase 3-C iPad-portrait stage size, and the Hatti logo in place of the upstream Scratch logo.Conflicts Resolved (highlights)
package*.json(root + 5 packages)reduxandreact/react-domas direct deps so webpack resolves them (peer-only is no longer enough)package-lock.jsonnpm installcontainers/blocks.jsxcomponentWillUnmountto be defensivecontainers/backpack.jsxbackpackStorage.save/list/deleteflow; localStorage path now lives inLegacyBackpackStoragelib/legacy-storage.tsLegacyBackpackStoragelib/alerts/index.jsxserialization/sb3.jsengine/blocks.js// eslint-disable-next-line @stylistic/max-lencomments to add)eslint.config.mjs(vm)tap-snapshots/**/*webpack.config.js(gui + vm)require.resolve('scratch-blocks/package.json')with'../../node_modules/scratch-blocks/media';scratch-blocks/dist/vertical.js→scratch-blocksfor expose-loaderTest Results
npm run lint— clean (errors 0, warnings 0, prettier OK)npm run build:dev— succeeds (only non-blockingjsdom/node-fetchoptional-native warnings remain)Manual DoD verification (Playwright, 1280×900)
Remaining console messages (non-fatal):
MISSING_TRANSLATIONforgui.stageHeader.thumbnailTooltipTitleandgui.stageHeader.saveThumbnailMessage— new upstream strings not yet inja.jskeywarning inside upstreamConfirmationPrompt— upstream-side issue, not blockingHigh-Risk Areas (for code review)
scratch-blocks v1 → v2 の API 移行は 441 ファイルに渡るため、レビュー時は以下の 挙動リスクが高い領域 を優先して spot-check してください:
Toolbox refresh / category click
make-toolbox-xml.jsでid="motion"→toolboxitemid="motion"に変更 (v2 の category lookup が toolboxitemid を期待)containers/blocks.jsxのhandleCategorySelectedが v1setSelectedCategoryByIdから v2getToolbox().setSelectedItem(getToolboxItemById(...))に変更Flyout block click → 値レポートツールチップ
scratch-vm/engine/blocks.jsで v1e.element === 'stackclick'→ v2case 'click'(e.targetType, e.blockId) に置き換えdata-idからscopeForFlyoutBlock(opcode)に変更 (v2 では data-id は unique block id)Custom procedures (My Blocks)
ScratchBlocks.Procedures→ScratchBlocks.ScratchProceduresへの renameWorkspace comments / sb3 deserialization
block_comment_*系のイベント名を v2 が削除しcomment_*に統一Drag / palette toggle
toolbox.getWidth() + (flyout?.getWidth?.() ?? 0)に変更 (v2 で toolbox.getWidth が category column のみ返すように)注意: AudioContext autoplay policy 関連は
?tab=sounds直接アクセスで触らないこと。詳細は.claude/rules/scratch-gui/e2e-test.md参照。TODO After Merge
gui.stageHeader.thumbnailTooltipTitle/saveThumbnailMessageinsrc/locales/ja.jsandja-Hira.jsdocs/<feature>/screenshots/after preview deploy:.claude/rules/scratch-gui/smalruby-markers.mdto list the new markers inlegacy-backpack-storage.tsRelated
🤖 Generated with
/upstream-mergeworkflow