概要
scratch.mit.edu で実装されている拡張機能のうち、スモウルビーでの対応が十分ではないものや、現在は入手が難しいデバイスの拡張機能をデフォルト状態で非表示にする機能を追加します。
拡張機能モーダルのタイトルバーに「すべての拡張機能を表示」チェックボックスを追加し、ユーザーがチェックを入れると非表示の拡張機能も表示されるようにします。この設定は Redux で状態管理するとともに、ローカルストレージに保存して次回起動時にも設定を引き継げるようにします。
背景
現在、以下の拡張機能が表示されていますが、一部はスモウルビーでの対応が不十分、またはデバイスの入手が困難です:
- Makey Makey
- micro:bit (標準版)
- LEGO MINDSTORMS EV3
- LEGO BOOST
- LEGO Education WeDo 2.0
- Go Direct Force & Acceleration
これらをデフォルトで非表示にすることで、ユーザーが利用可能な拡張機能に集中できるようにします。
仕様
デフォルトで表示する拡張機能
以下の拡張機能のみをデフォルトで表示:
- Music (
music)
- Pen (
pen)
- Video Sensing (
videoSensing)
- Text to Speech (
text2speech)
- Translate (
translate)
- Mesh (
mesh)
- Smalrubot S1 (
smalrubotS1)
- Microbit More (
microbitMore) - v2-0.2.5
- Smalruby Koshien (
koshien)
デフォルトで非表示にする拡張機能
以下の拡張機能をデフォルトで非表示:
- Makey Makey (
makeymakey)
- micro:bit (
microbit)
- LEGO MINDSTORMS EV3 (
ev3)
- LEGO BOOST (
boost)
- LEGO Education WeDo 2.0 (
wedo2)
- Go Direct Force & Acceleration (
gdxfor)
UI 仕様
拡張機能モーダルのタイトルバーに以下のチェックボックスを追加:
配置位置:
動作:
- チェックを入れると、非表示の拡張機能も表示される
- チェックを外すと、デフォルト表示の拡張機能のみ表示される
- チェック状態はローカルストレージに保存され、次回起動時にも引き継がれる
多言語対応
react-intl の FormattedMessage を使用して多言語対応:
<FormattedMessage
defaultMessage="Show all extensions"
description="Checkbox label to show all extensions including hidden ones"
id="gui.extensionLibrary.showAllExtensions"
/>
アーキテクチャ改善:headerActions パターン
現在の問題点
既存の実装(および最初の提案)では、Modal コンポーネントに拡張機能固有のロジック(チェックボックス、リロードボタンなど)を直接追加しています:
// 問題のあるアプローチ
const ModalComponent = props => (
<div className={styles.header}>
{/* Modal が拡張機能固有のロジックを知っている */}
{props.showAllExtensionsCheckbox ? <checkbox> : null}
{props.onReload ? <reload button> : null}
</div>
);
問題点:
- ❌ Modal が特定のアクションの知識を持つ
- ❌ 新しいアクションを追加するたびに Modal を修正が必要
- ❌ 使用しないモーダルでも不要な props を受け取る
- ❌ メンテナンス性が低い
改善案:headerActions Props パターン
Modal をジェネリックなコンポーネントとして保ち、各モーダルが自分のカスタムアクションを ReactNode として渡す方法を採用します。
メリット:
- ✅ Modal はジェネリックで、特定のアクションの知識を持たない
- ✅ 各モーダルが自分のアクションを完全にコントロール
- ✅ 新しいアクションを追加しても Modal を変更する必要がない
- ✅ 既存の Reload/Stop ボタンも同じパターンで統一できる
- ✅ テスト性が向上(各モーダルが独立してテスト可能)
実装概要
// 改善されたアプローチ
const ModalComponent = props => (
<div className={styles.header}>
{props.onHelp ? <help button> : null}
<div className={styles.headerItemTitle}>{props.contentLabel}</div>
{/* ジェネリックな headerActions - 各モーダルが定義 */}
{props.headerActions}
<div className={styles.headerItemClose}>
<close button>
</div>
</div>
);
各モーダルが自分のアクションを定義:
// LibraryComponent の例
const headerActions = (
<label className={styles.showAllExtensionsLabel}>
<input type="checkbox" ... />
<FormattedMessage ... />
</label>
);
<Modal headerActions={headerActions}>
// KoshienTestModal の例
const headerActions = (
<Button onClick={handleReload}>
<FormattedMessage id="gui.modal.reload" />
</Button>
);
<Modal headerActions={headerActions}>
実装詳細
1. Redux Reducer/Actions の追加
ファイル: gui/smalruby3-gui/src/reducers/extension-filter.js (新規作成)
const TOGGLE_SHOW_ALL_EXTENSIONS = 'scratch-gui/extension-filter/TOGGLE_SHOW_ALL_EXTENSIONS';
const SHOW_ALL_EXTENSIONS_KEY = 'smalruby:showAllExtensions';
const savedShowAllExtensions = typeof window !== 'undefined' && window.localStorage ?
window.localStorage.getItem(SHOW_ALL_EXTENSIONS_KEY) === 'true' : false;
const initialState = {
showAllExtensions: savedShowAllExtensions
};
const reducer = function (state, action) {
if (typeof state === 'undefined') state = initialState;
switch (action.type) {
case TOGGLE_SHOW_ALL_EXTENSIONS:
if (typeof window !== 'undefined' && window.localStorage) {
window.localStorage.setItem(SHOW_ALL_EXTENSIONS_KEY, action.showAllExtensions);
}
return Object.assign({}, state, {
showAllExtensions: action.showAllExtensions
});
default:
return state;
}
};
const toggleShowAllExtensions = function (showAllExtensions) {
return {
type: TOGGLE_SHOW_ALL_EXTENSIONS,
showAllExtensions: showAllExtensions
};
};
export {
reducer as default,
initialState as extensionFilterInitialState,
toggleShowAllExtensions
};
参考: gui/smalruby3-gui/src/reducers/ruby-code.js のローカルストレージパターン
Redux store への統合:
gui/smalruby3-gui/src/reducers/gui.js に追加
import extensionFilterReducer, {extensionFilterInitialState} from './extension-filter';
const guiInitialState = {
// ... 既存の state
extensionFilter: extensionFilterInitialState
};
const guiReducer = combineReducers({
// ... 既存の reducers
extensionFilter: extensionFilterReducer
});
2. ExtensionLibrary の修正
ファイル: gui/smalruby3-gui/src/containers/extension-library.jsx
フィルタリングロジックと headerActions を追加:
import React from 'react';
import {connect} from 'react-redux';
import {FormattedMessage} from 'react-intl';
import {toggleShowAllExtensions} from '../reducers/extension-filter';
// 非表示にする拡張機能のリスト
const DEFAULT_HIDDEN_EXTENSIONS = [
'makeymakey',
'microbit',
'ev3',
'boost',
'wedo2',
'gdxfor'
];
class ExtensionLibrary extends React.PureComponent {
constructor (props) {
super(props);
// ... 既存の constructor
bindAll(this, [
'handleItemSelect',
'handleToggleShowAllExtensions'
]);
}
handleToggleShowAllExtensions (event) {
this.props.onToggleShowAllExtensions(event.target.checked);
}
render () {
const query = new URLSearchParams(window.location.search);
const extensionsParam = query.get('extensions') || '';
const showMeshV2 = extensionsParam.split(',').includes('meshV2');
const extensionLibraryThumbnailData = extensionLibraryContent
.filter(extension => {
// meshV2 の既存フィルタリング
if (extension.extensionId === 'meshV2' && !showMeshV2) {
return false;
}
// 新しいフィルタリング:showAllExtensions が false の場合、非表示リストをフィルタ
if (!this.props.showAllExtensions &&
DEFAULT_HIDDEN_EXTENSIONS.includes(extension.extensionId)) {
return false;
}
return true;
})
.map(extension => ({
rawURL: extension.iconURL || extensionIcon,
...extension
}));
// headerActions を定義(LibraryComponent が完全にコントロール)
const headerActions = (
<label className={styles.showAllExtensionsLabel}>
<input
checked={this.props.showAllExtensions}
className={styles.showAllExtensionsCheckbox}
type="checkbox"
onChange={this.handleToggleShowAllExtensions}
/>
<FormattedMessage
defaultMessage="Show all extensions"
description="Checkbox label to show all extensions including hidden ones"
id="gui.extensionLibrary.showAllExtensions"
/>
</label>
);
return (
<LibraryComponent
data={extensionLibraryThumbnailData}
filterable={false}
headerActions={headerActions}
id="extensionLibrary"
title={this.props.intl.formatMessage(messages.extensionTitle)}
visible={this.props.visible}
onItemSelected={this.handleItemSelect}
onRequestClose={this.props.onRequestClose}
/>
);
}
}
ExtensionLibrary.propTypes = {
intl: intlShape.isRequired,
onCategorySelected: PropTypes.func,
onRequestClose: PropTypes.func,
onToggleShowAllExtensions: PropTypes.func,
showAllExtensions: PropTypes.bool,
visible: PropTypes.bool,
vm: PropTypes.instanceOf(VM).isRequired
};
const mapStateToProps = state => ({
showAllExtensions: state.scratchGui.extensionFilter.showAllExtensions
});
const mapDispatchToProps = dispatch => ({
onToggleShowAllExtensions: showAll => dispatch(toggleShowAllExtensions(showAll))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(injectIntl(ExtensionLibrary));
CSS の追加:
gui/smalruby3-gui/src/containers/extension-library.css (新規作成または既存ファイルに追加)
@import "../components/modal/modal.css";
.show-all-extensions-label {
display: flex;
align-items: center;
font-size: 0.75rem;
font-weight: normal;
cursor: pointer;
user-select: none;
color: $ui-white;
white-space: nowrap;
padding: 1rem;
}
.show-all-extensions-checkbox {
cursor: pointer;
}
[dir="ltr"] .show-all-extensions-checkbox {
margin-right: 0.5rem;
}
[dir="rtl"] .show-all-extensions-checkbox {
margin-left: 0.5rem;
}
3. LibraryComponent の拡張
ファイル: gui/smalruby3-gui/src/components/library/library.jsx
headerActions を Modal に渡す:
LibraryComponent.propTypes = {
data: PropTypes.arrayOf(/* ... */),
filterable: PropTypes.bool,
headerActions: PropTypes.node, // 追加
id: PropTypes.string.isRequired,
intl: intlShape.isRequired,
onItemMouseEnter: PropTypes.func,
onItemMouseLeave: PropTypes.func,
onItemSelected: PropTypes.func,
onRequestClose: PropTypes.func,
setStopHandler: PropTypes.func,
showPlayButton: PropTypes.bool,
tags: PropTypes.arrayOf(PropTypes.shape(TagButton.propTypes)),
title: PropTypes.string.isRequired
};
// render メソッド内の Modal
return (
<Modal
fullScreen
contentLabel={this.props.title}
headerActions={this.props.headerActions}
id={this.props.id}
onRequestClose={this.handleClose}
>
{/* 既存の内容 */}
</Modal>
);
4. Modal Component の拡張
ファイル: gui/smalruby3-gui/src/components/modal/modal.jsx
headerActions を受け取ってレンダリング:
const ModalComponent = props => (
<ReactModal
isOpen
className={classNames(styles.modalContent, props.className, {
[styles.fullScreen]: props.fullScreen
})}
contentLabel={props.contentLabel.toString()}
overlayClassName={styles.modalOverlay}
onRequestClose={props.onRequestClose}
>
<Box
className={styles.box}
dir={props.isRtl ? 'rtl' : 'ltr'}
direction="column"
grow={1}
>
<div className={classNames(styles.header, props.headerClassName)}>
{/* 既存の Help ボタン */}
{props.onHelp ? (
<div className={classNames(styles.headerItem, styles.headerItemHelp)}>
<Button
className={styles.helpButton}
iconSrc={helpIcon}
onClick={props.onHelp}
>
<FormattedMessage
defaultMessage="Help"
description="Help button in modal"
id="gui.modal.help"
/>
</Button>
</div>
) : null}
{/* タイトル */}
<div className={classNames(styles.headerItem, styles.headerItemTitle)}>
{props.headerImage ? (
<img
className={styles.headerImage}
src={props.headerImage}
/>
) : null}
{props.contentLabel}
</div>
{/* 新規: ジェネリックな headerActions */}
{props.headerActions ? (
<div className={classNames(styles.headerItem, styles.headerItemActions)}>
{props.headerActions}
</div>
) : null}
{/* 既存の Reload/Stop ボタン(後方互換性のため残す) */}
{props.loading && props.onStop ? (
<div className={classNames(styles.headerItem, styles.headerItemReload)}>
<Button
className={styles.reloadButton}
iconClassName={styles.stopIcon}
iconSrc={stopIcon}
onClick={props.onStop}
>
<FormattedMessage
defaultMessage="Stop"
description="Stop button in modal"
id="gui.modal.stop"
/>
</Button>
</div>
) : (props.onReload ? (
<div className={classNames(styles.headerItem, styles.headerItemReload)}>
<Button
className={styles.reloadButton}
iconSrc={reloadIcon}
onClick={props.onReload}
>
<FormattedMessage
defaultMessage="Reload"
description="Reload button in modal"
id="gui.modal.reload"
/>
</Button>
</div>
) : null)}
{/* 既存の Close ボタン */}
<div className={classNames(styles.headerItem, styles.headerItemClose)}>
{props.fullScreen ? (
<Button
className={styles.backButton}
iconSrc={backIcon}
onClick={props.onRequestClose}
>
<FormattedMessage
defaultMessage="Back"
description="Back button in modal"
id="gui.modal.back"
/>
</Button>
) : (
<CloseButton
size={CloseButton.SIZE_LARGE}
onClick={props.onRequestClose}
/>
)}
</div>
</div>
{props.loading ? (
<div className={styles.progressBar}>
<div className={styles.progressBarValue} />
</div>
) : null}
{props.children}
</Box>
</ReactModal>
);
ModalComponent.propTypes = {
children: PropTypes.node,
className: PropTypes.string,
contentLabel: PropTypes.oneOfType([
PropTypes.string,
PropTypes.object
]).isRequired,
fullScreen: PropTypes.bool,
headerActions: PropTypes.node, // 追加
headerClassName: PropTypes.string,
headerImage: PropTypes.string,
isRtl: PropTypes.bool,
loading: PropTypes.bool,
onHelp: PropTypes.func,
onReload: PropTypes.func, // 後方互換性のため残す
onRequestClose: PropTypes.func,
onStop: PropTypes.func // 後方互換性のため残す
};
注意点:
onReload と onStop は後方互換性のため残す
- 将来的には KoshienTestModal も headerActions パターンに移行可能
5. CSS の追加
ファイル: gui/smalruby3-gui/src/components/modal/modal.css
headerActions のスタイル:
/* Header actions(ジェネリック) */
.header-item-actions {
padding: 0;
z-index: 1;
}
[dir="ltr"] .header-item-actions {
margin-left: -4.75rem;
}
[dir="rtl"] .header-item-actions {
margin-right: -4.75rem;
}
注意点:
header-item-reload と同じスタイルパターン
- ジェネリックなため、内部のスタイルは各モーダルが定義
6. 翻訳ファイルの更新
各言語の翻訳ファイルに以下を追加:
英語 (en.json):
{
"gui.extensionLibrary.showAllExtensions": "Show all extensions"
}
日本語 (ja.json / ja-Hira.json):
{
"gui.extensionLibrary.showAllExtensions": "すべての拡張機能を表示"
}
KoshienTestModal の移行(オプション)
headerActions パターンを採用することで、KoshienTestModal も同様に改善できます。
Before(現在の実装)
<Modal
loading={loading}
onReload={handleReload}
onStop={handleStop}
onRequestClose={onRequestClose}
>
After(headerActions パターン)
const headerActions = loading ? (
<Button
className={styles.stopButton}
iconClassName={styles.stopIcon}
iconSrc={stopIcon}
onClick={handleStop}
>
<FormattedMessage
defaultMessage="Stop"
description="Stop button in modal"
id="gui.modal.stop"
/>
</Button>
) : (
<Button
className={styles.reloadButton}
iconSrc={reloadIcon}
onClick={handleReload}
>
<FormattedMessage
defaultMessage="Reload"
description="Reload button in modal"
id="gui.modal.reload"
/>
</Button>
);
<Modal
headerActions={headerActions}
loading={loading}
onRequestClose={onRequestClose}
>
メリット:
- KoshienTestModal が自分のボタンのスタイルを完全にコントロール
- Modal は loading state の知識を持つ必要がない
- より一貫性のあるアーキテクチャ
改修案
案1: 拡張機能メタデータに defaultHidden フラグを追加
メリット:
- よりスケーラブルで保守性が高い
- 拡張機能の定義と表示制御が同じ場所にある
- ハードコードされたリストを避けられる
実装例:
// gui/smalruby3-gui/src/lib/libraries/extensions/index.jsx
const extensions = [
{
name: 'Music',
extensionId: 'music',
// defaultHidden を追加しない(デフォルト表示)
...
},
{
name: 'Makey Makey',
extensionId: 'makeymakey',
defaultHidden: true, // デフォルトで非表示
...
}
];
フィルタリング:
.filter(extension => {
if (extension.extensionId === 'meshV2' && !showMeshV2) return false;
if (!this.props.showAllExtensions && extension.defaultHidden) return false;
return true;
})
案2: URL パラメータでの制御
メリット:
- デバッグやデモ時に便利
- ローカルストレージを上書きできる
- 既存の
meshV2 URL パラメータパターンと一貫性がある
実装例:
const query = new URLSearchParams(window.location.search);
const showAllExtensionsParam = query.get('showAllExtensions');
const showAllExtensions = showAllExtensionsParam === 'true' ? true :
showAllExtensionsParam === 'false' ? false :
this.props.showAllExtensions;
テスト計画
手動テスト
-
デフォルト表示の確認
- 拡張機能モーダルを開く
- デフォルトで9個の拡張機能のみが表示されることを確認
- 非表示の6個の拡張機能が表示されないことを確認
-
チェックボックス機能の確認
- チェックボックスをオンにする
- すべての拡張機能(15個)が表示されることを確認
- チェックボックスをオフにする
- デフォルトの9個の拡張機能のみに戻ることを確認
-
ローカルストレージの永続性確認
- チェックボックスをオンにする
- ページをリロード
- チェックボックスがオンのままで、すべての拡張機能が表示されることを確認
- チェックボックスをオフにする
- ページをリロード
- チェックボックスがオフのままで、デフォルト表示になることを確認
-
多言語対応の確認
- 言語を英語に変更し、チェックボックスのラベルを確認
- 言語を日本語に変更し、チェックボックスのラベルを確認
-
既存機能への影響確認
- Mesh V2 の URL パラメータ制御が引き続き動作することを確認
- 拡張機能の選択・読み込みが正常に動作することを確認
- KoshienTestModal のリロードボタンが引き続き動作することを確認(後方互換性)
-
レイアウト確認
- チェックボックスがタイトルとCloseボタンの間に表示されることを確認
- RTL(右から左)レイアウトでも正しく表示されることを確認
ESLint チェック
docker compose run --rm gui bash -c "cd /app/gui/smalruby3-gui && npm run test:lint"
ビルド確認
docker compose run --rm gui bash -c "cd /app/gui/smalruby3-gui && npm run build"
セキュリティ考慮事項
- ローカルストレージの値は boolean のみ(XSS リスクなし)
- ユーザー入力は checkbox のクリックイベントのみ(インジェクションリスクなし)
- headerActions は ReactNode のため、React の自動エスケープが適用される
- 既存のセキュリティパターンに従う
アクセシビリティ
- checkbox に FormattedMessage でラベルを提供
- label 要素で checkbox とテキストを関連付け
- キーボード操作対応(React の標準 checkbox で対応済み)
- RTL(右から左)レイアウト対応
完了条件
関連ファイル
新規作成
gui/smalruby3-gui/src/reducers/extension-filter.js
gui/smalruby3-gui/src/containers/extension-library.css
修正
gui/smalruby3-gui/src/reducers/gui.js (Redux store に統合)
gui/smalruby3-gui/src/containers/extension-library.jsx (フィルタリング + headerActions)
gui/smalruby3-gui/src/components/library/library.jsx (headerActions props 追加)
gui/smalruby3-gui/src/components/modal/modal.jsx (headerActions サポート)
gui/smalruby3-gui/src/components/modal/modal.css (headerActions スタイル)
- 翻訳ファイル (en.json, ja.json, ja-Hira.json)
参照のみ
gui/smalruby3-gui/src/lib/libraries/extensions/index.jsx
gui/smalruby3-gui/src/components/koshien-test-modal/koshien-test-modal.jsx (移行可能な例)
gui/smalruby3-gui/src/reducers/ruby-code.js (ローカルストレージパターンの参考)
将来の改善
KoshienTestModal の移行
headerActions パターンの有効性が確認できたら、KoshienTestModal も同様に移行することを推奨します:
- KoshienTestModal で headerActions を定義
- Modal から
onReload と onStop props を削除(破壊的変更)
- より一貫性のあるアーキテクチャを実現
この移行は別の Issue として扱うことを推奨します。
概要
scratch.mit.edu で実装されている拡張機能のうち、スモウルビーでの対応が十分ではないものや、現在は入手が難しいデバイスの拡張機能をデフォルト状態で非表示にする機能を追加します。
拡張機能モーダルのタイトルバーに「すべての拡張機能を表示」チェックボックスを追加し、ユーザーがチェックを入れると非表示の拡張機能も表示されるようにします。この設定は Redux で状態管理するとともに、ローカルストレージに保存して次回起動時にも設定を引き継げるようにします。
背景
現在、以下の拡張機能が表示されていますが、一部はスモウルビーでの対応が不十分、またはデバイスの入手が困難です:
これらをデフォルトで非表示にすることで、ユーザーが利用可能な拡張機能に集中できるようにします。
仕様
デフォルトで表示する拡張機能
以下の拡張機能のみをデフォルトで表示:
music)pen)videoSensing)text2speech)translate)mesh)smalrubotS1)microbitMore) - v2-0.2.5koshien)デフォルトで非表示にする拡張機能
以下の拡張機能をデフォルトで非表示:
makeymakey)microbit)ev3)boost)wedo2)gdxfor)UI 仕様
拡張機能モーダルのタイトルバーに以下のチェックボックスを追加:
配置位置:
動作:
多言語対応
react-intl の FormattedMessage を使用して多言語対応:
アーキテクチャ改善:headerActions パターン
現在の問題点
既存の実装(および最初の提案)では、Modal コンポーネントに拡張機能固有のロジック(チェックボックス、リロードボタンなど)を直接追加しています:
問題点:
改善案:headerActions Props パターン
Modal をジェネリックなコンポーネントとして保ち、各モーダルが自分のカスタムアクションを ReactNode として渡す方法を採用します。
メリット:
実装概要
各モーダルが自分のアクションを定義:
実装詳細
1. Redux Reducer/Actions の追加
ファイル:
gui/smalruby3-gui/src/reducers/extension-filter.js(新規作成)参考:
gui/smalruby3-gui/src/reducers/ruby-code.jsのローカルストレージパターンRedux store への統合:
gui/smalruby3-gui/src/reducers/gui.jsに追加2. ExtensionLibrary の修正
ファイル:
gui/smalruby3-gui/src/containers/extension-library.jsxフィルタリングロジックと headerActions を追加:
CSS の追加:
gui/smalruby3-gui/src/containers/extension-library.css(新規作成または既存ファイルに追加)3. LibraryComponent の拡張
ファイル:
gui/smalruby3-gui/src/components/library/library.jsxheaderActions を Modal に渡す:
4. Modal Component の拡張
ファイル:
gui/smalruby3-gui/src/components/modal/modal.jsxheaderActions を受け取ってレンダリング:
注意点:
onReloadとonStopは後方互換性のため残す5. CSS の追加
ファイル:
gui/smalruby3-gui/src/components/modal/modal.cssheaderActions のスタイル:
注意点:
header-item-reloadと同じスタイルパターン6. 翻訳ファイルの更新
各言語の翻訳ファイルに以下を追加:
英語 (en.json):
{ "gui.extensionLibrary.showAllExtensions": "Show all extensions" }日本語 (ja.json / ja-Hira.json):
{ "gui.extensionLibrary.showAllExtensions": "すべての拡張機能を表示" }KoshienTestModal の移行(オプション)
headerActions パターンを採用することで、KoshienTestModal も同様に改善できます。
Before(現在の実装)
After(headerActions パターン)
メリット:
改修案
案1: 拡張機能メタデータに
defaultHiddenフラグを追加メリット:
実装例:
フィルタリング:
案2: URL パラメータでの制御
メリット:
meshV2URL パラメータパターンと一貫性がある実装例:
テスト計画
手動テスト
デフォルト表示の確認
チェックボックス機能の確認
ローカルストレージの永続性確認
多言語対応の確認
既存機能への影響確認
レイアウト確認
ESLint チェック
docker compose run --rm gui bash -c "cd /app/gui/smalruby3-gui && npm run test:lint"ビルド確認
docker compose run --rm gui bash -c "cd /app/gui/smalruby3-gui && npm run build"セキュリティ考慮事項
アクセシビリティ
完了条件
extension-filter.jsを作成ExtensionLibraryにフィルタリングロジックを追加ExtensionLibraryに headerActions を実装ExtensionLibraryを Redux に connectExtensionLibraryの CSS を追加LibraryComponentに headerActions props を追加Modalコンポーネントに headerActions を追加Modalの CSS を追加関連ファイル
新規作成
gui/smalruby3-gui/src/reducers/extension-filter.jsgui/smalruby3-gui/src/containers/extension-library.css修正
gui/smalruby3-gui/src/reducers/gui.js(Redux store に統合)gui/smalruby3-gui/src/containers/extension-library.jsx(フィルタリング + headerActions)gui/smalruby3-gui/src/components/library/library.jsx(headerActions props 追加)gui/smalruby3-gui/src/components/modal/modal.jsx(headerActions サポート)gui/smalruby3-gui/src/components/modal/modal.css(headerActions スタイル)参照のみ
gui/smalruby3-gui/src/lib/libraries/extensions/index.jsxgui/smalruby3-gui/src/components/koshien-test-modal/koshien-test-modal.jsx(移行可能な例)gui/smalruby3-gui/src/reducers/ruby-code.js(ローカルストレージパターンの参考)将来の改善
KoshienTestModal の移行
headerActions パターンの有効性が確認できたら、KoshienTestModal も同様に移行することを推奨します:
onReloadとonStopprops を削除(破壊的変更)この移行は別の Issue として扱うことを推奨します。