本文へ移動
cccskills
無料GitHub で公開

blockly

Blockly visual programming context for the Megahub project. Use when adding, modifying, or debugging custom Blockly blocks, the Lua code generator, the toolbox, block colors, or the BLOCKS.md documentation generation script.

インストール方法を見る

含まれるファイル(1)

  • SKILL.md10.6 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Blockly Integration — Megahub IDE

Overview

  • Blockly 12.3.1 with the built-in Lua code generator (blockly/lua)
  • Custom Web Component <custom-blockly> in frontend/src/components/blockly/component.js
  • Each custom block is a self-contained JS file with definition + Lua generator + color
  • All 25 custom blocks auto-register; BLOCKS.md is auto-generated via Puppeteer

File Structure

frontend/src/components/blockly/
  component.js          Main component — init, toolbox, workspace, Lua generation
  component.html        Minimal web component template
  colors.js             Color hue constants for all categories (0–360)
  mh_*.js               20 Megahub core blocks (motors, GPIO, threads, debug)
  lego_*.js             2 LEGO Powered Up blocks
  ui_*.js               1 UI display block

Block Definition Pattern

Every block is one file. All fields are required unless noted:

export const definition = {
    category: 'Category Name',      // Groups this block in the toolbox
    colour: colorConstant,          // Import from colors.js — never hardcode
    blockdefinition: {              // Standard Blockly JSON definition
        type: 'mh_block_name',      // Must match filename (minus .js)
        message0: 'label %1 %2',    // %N = input/field placeholders
        args0: [ /* inputs */ ],
        previousStatement: null,    // Include for statement blocks
        nextStatement: null,        // Include for statement blocks
        // OR: output: null         // For value blocks that return a value
        tooltip: 'Description for BLOCKS.md and hover UI',
        helpUrl: ''
    },
    generator: (block, generator) => {
        // Return string + "\n" for statement blocks
        // Return [ "code", precedence ] for value blocks
    },
    inputsForToolbox: { /* optional shadow defaults */ }
};

Statement block generator pattern

generator: (block, generator) => {
    const val = generator.valueToCode(block, 'VALUE', 0);
    const stmt = generator.statementToCode(block, 'DO');
    const field = block.getFieldValue('FIELD_NAME');
    return `myFunction(${val}, ${field})\n`;
}

Value block generator pattern

generator: (block, generator) => {
    const val = block.getFieldValue('VALUE');
    return [ val, 0 ];   // [ code, precedence ]
}

Naming Conventions

PrefixCategoryExample
mh_Megahub coremh_set_motor_speed, mh_wait, mh_startthread
lego_LEGO Powered Uplego_get_mode_dataset, lego_select_mode
ui_User interfaceui_show_value
  • All lowercase, underscores only
  • Block type ID must match the JS filename (e.g., mh_wait.js → type: 'mh_wait')

Color System (colors.js)

Colors are Blockly hue values (0–360). Always import — never hardcode a number.

ConstantHueCategory
colorControlFlow20Control flow
colorLogic40Logic
colorMath60Math
colorText80Text
colorLists100Lists
colorIO120I/O
colorLego140LEGO©
colorGamepad160Gamepad
colorFastLED180FastLED
colorIMU200IMU
colorUI220UI
colorAlgorithms230Algorithms
colorDebug240Debug

Adding a New Block

  1. Create frontend/src/components/blockly/mh_myblock.js using the pattern above
  2. Import and register in all three files:
    • frontend/src/components/blockly/component.js — add import + entry in customBlocks
    • frontend/scripts/block-renderer.js — add import + entry in customBlocks
    • frontend/scripts/generate-block-docs-simple.js — add dynamic import in blockModules + entry in customBlocks
  3. Toolbox: automatic — generateToolbox() groups by category field
  4. Write unit tests — REQUIRED for every new block (see below)
  5. Test in dev mode: npm run dev or npm test
  6. Regenerate docs — REQUIRED after every block add/modify/remove:
    cd frontend
    npm run generate-docs
    
    This regenerates BLOCKS.md and all PNG images in docs/blocks/. Never skip this step.

Unit Tests — REQUIRED for Every New Block

Every new block file must have a corresponding test file. This is a hard requirement.

Location: frontend/test/blockly/{block_type}.test.js

Test file must cover:

  1. Generator output — correct Lua string emitted for representative inputs
  2. Field values — all dropdown options generate the correct Lua variant
  3. Statement vs. value form (for dual-mode blocks like mh_startthread pattern) — both forms generate correct output
  4. Edge cases — empty inputs, zero values, boundary field options

Minimal test pattern:

import { describe, it, expect } from 'vitest';
// Import the block definition
import { definition } from '../../../../src/components/blockly/mh_myblock.js';

describe('mh_myblock generator', () => {
    it('generates correct Lua for basic case', () => {
        const mockBlock = {
            getFieldValue: (name) => name === 'MY_FIELD' ? 'VALUE' : '',
            outputConnection: null,
        };
        const mockGenerator = {
            valueToCode: (_b, _name, _prec) => '42',
        };
        const result = definition.generator(mockBlock, mockGenerator);
        expect(result).toBe('myFunction(42, "VALUE")\n');
    });
});

Run tests:

cd frontend
npm test

When to create tests: Immediately after creating the block file, before registering it in component.js. Tests run in CI — a block without tests is incomplete.

The component.js registration loop handles everything:

// Blockly block definition
Blockly.Blocks[type].init = function() { this.jsonInit(def.blockdefinition); }
// Lua generator
luaGenerator.forBlock[type] = (block) => def.generator(block, luaGenerator);

Lua Code Generation

The workspace generates Lua code on every change:

// In component.js — called on workspace change events
generateLUAPreview() {
    const code = luaGenerator.workspaceToCode(this.workspace);
    // code is the complete Lua script
}

Generated Lua script structure (from mh_init and mh_startthread blocks):

hub.init(function()
    -- initialization block contents
end)

hub.startthread('Thread Name', blockId, stackSize, false, function()
    -- thread loop contents
end)

BLOCKS.md Auto-Generation

BLOCKS.md is automatically generated — never edit it manually.

Command:

cd frontend
npm run generate-docs

How it works (frontend/scripts/generate-block-docs-simple.js):

  1. Runs vite build to produce a renderer bundle
  2. Launches Puppeteer (headless Chrome)
  3. Loads block-renderer.html which runs Blockly in the browser
  4. For each block: renders to SVG → screenshot to PNG at 2× DPI
  5. Saves PNGs to docs/blocks/{category}/{blocktype}.png
  6. Generates Markdown with: block image, tooltip description, type, message template, inputs table, fields table
  7. Appends a statistics summary (block counts by type and category)

Output structure:

docs/blocks/
  control flow/mh_init.png
  lego©/lego_get_mode_dataset.png
  ...
BLOCKS.md   ← regenerated in full

When to regenerate: after adding, removing, or changing any block definition, tooltip, inputs, or fields. The tooltip field in blockdefinition becomes the description in BLOCKS.md — keep it informative.


Stateful Algorithm Block Pattern

All stateful algorithm blocks in the Algorithms category follow an explicit handle pattern. This is a hard convention — never use block.id as an implicit state key.

Pattern: Init + Compute (same as PID and DR)

A stateful algorithm requires two Blockly blocks:

  1. Init block (mh_alg_*_init.js) — creates a filter/controller instance, returns a handle string. The user stores this in a variable.
  2. Compute block (mh_alg_*.js) — takes the handle as first input, plus algorithm parameters.

Firmware side (libluaalg.cpp)

// State map keyed by handle string ("ma_0", "ma_1", ...)
static std::map<std::string, MyState> myStates;
static int myHandleCounter = 0;

// Init: creates instance, returns handle
int alg_init_my(lua_State* L) {
    std::string handle = "my_" + std::to_string(myHandleCounter++);
    myStates[handle] = MyState{};
    lua_pushstring(L, handle.c_str());
    return 1;
}

// Compute: looks up by handle
int alg_my(lua_State* L) {
    const char* handle = luaL_checkstring(L, 1);
    auto it = myStates.find(handle);
    if (it == myStates.end()) {
        WARN("alg_my: handle '%s' not found", handle);
        lua_pushnumber(L, 0.0);
        return 1;
    }
    // ... use it->second ...
}

// Clear all: called in alg_library() at init
int alg_clear_all_my(lua_State* L) {
    myStates.clear();
    myHandleCounter = 0;
    return 0;
}

Register all three in alg_library(), and call alg_clear_all_my at the top of alg_library().

Blockly side

Init block — no inputs, output is a handle value:

generator: (block, generator) => {
    return [`alg.initMy()`, 0];
},

Compute block — HANDLE is first input, uses variables_get shadow:

inputsForToolbox: {
    HANDLE: {
        shadow: { type: 'variables_get', fields: { VAR: { name: 'myFilter' } } },
    },
    // ... other inputs ...
},
generator: (block, generator) => {
    const handle = generator.valueToCode(block, 'HANDLE', 0) || '"my_0"';
    // ...
    return [`alg.my(${handle}, ...)`, 0];
},

Registration

Both blocks must be added to all three registration files:

  • frontend/src/components/blockly/component.js
  • frontend/scripts/block-renderer.js
  • frontend/scripts/generate-block-docs-simple.js

Stateless algorithms (alg.map)

Stateless algorithms (no persistent state) need only one block, no handle, no init, no clearAll. Register only the single block file.


Toolbox Toolbox Shadow Defaults (inputsForToolbox)

To pre-populate inputs in the toolbox with useful defaults, add inputsForToolbox:

inputsForToolbox: {
    VALUE: {
        shadow: {
            type: 'math_number',
            fields: { NUM: 500 }
        }
    }
}

The generateToolbox() function reads this and adds <shadow> elements automatically.

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

ble

無料

Bluetooth context for the Megahub project. Use when working on BLE GATT server, Classic Bluetooth HID host, Web Bluetooth API integration, btremote library, connection handling, or any Bluetooth-related firmware code.

日本語の概要は準備中です。原文の説明を表示しています。

mirkosertic/Megahub72026年9月29日 更新

Documentation structure and maintenance rules for the Megahub project. Use whenever making user-facing changes — adding features, changing behavior, fixing hardware compatibility, or modifying configuration. Describes which files to update, the README structure, and writing style.

日本語の概要は準備中です。原文の説明を表示しています。

mirkosertic/Megahub72026年9月29日 更新

Core firmware context for the Megahub ESP32 project. Use when working on PlatformIO configuration, build scripts, library structure, main.cpp, or general ESP32 firmware development. Covers build system, project structure, common pitfalls, and debugging.

日本語の概要は準備中です。原文の説明を表示しています。

mirkosertic/Megahub72026年9月29日 更新

frontend

無料

Frontend development context for the Megahub project. Use when working on the web-based IDE, Vite build system, Blockly visual programming, Web Bluetooth API integration, JavaScript frontend code, CSS/styles, UI components, or anything in the /frontend/ directory.

日本語の概要は準備中です。原文の説明を表示しています。

mirkosertic/Megahub72026年9月29日 更新

LEGO Powered Up protocol context for the Megahub project. Use when working on LEGO device communication, lpfuart library, UART parsing, motor control, sensor modes, device initialization, or debugging UART noise/corruption issues.

日本語の概要は準備中です。原文の説明を表示しています。

mirkosertic/Megahub72026年9月29日 更新

linting

無料

Code formatting and linting context for the Megahub project. Use when writing or reviewing code in C++ or JavaScript, or when running format/lint checks. Describes tool commands, style conventions, and when to apply them.

日本語の概要は準備中です。原文の説明を表示しています。

mirkosertic/Megahub72026年9月29日 更新

mirkosertic のスキルをすべて見る

このスキルの問題を報告する