Skip to content

feat: add Stack-chan WASM build support#402

Open
meganetaaan wants to merge 7 commits intodev/v1.0from
feat/wasm-stackchan-build
Open

feat: add Stack-chan WASM build support#402
meganetaaan wants to merge 7 commits intodev/v1.0from
feat/wasm-stackchan-build

Conversation

@meganetaaan
Copy link
Copy Markdown
Collaborator

@meganetaaan meganetaaan commented Apr 29, 2026

Summary

  • Add a Moddable wasm manifest for firmware/stackchan
  • Add wasm-safe stubs for hardware, network, speech, and default-mod entrypoints
  • Add npm run build:wasm to build mc.js/mc.wasm into web/simulator/

Verification

  • npm ci (firmware)
  • MODDABLE=/.local/share/moddable + emsdk 3.1.2 + FONTBM=/.local/bin/fontbm npm run build:wasm

Stacked follow-up will wire these artifacts into the deployed web page.

Do not merge yet; stacked CI deploy PR will target this branch.

Summary by CodeRabbit

  • New Features

    • WebAssembly build and browser simulator support with a WASM-targeted runtime, presets, renderer and utility manifests, plus simulator subsystems (drivers, LED, tone, microphone, network, preferences, and multiple TTS backends as stubs).
  • Chores

    • Build tooling extended to produce and stage WASM artifacts for the web simulator; CI setup enhanced to ensure required SDK and host tooling.
  • Stability

    • Startup now emits richer trace logs and surfaces initialization errors.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 29, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds WASM build tooling and manifests, many WASM-target stub modules (drivers, services, TTS, hardware shims), and a WASM startup branch that evaluates defaultMod.onLaunch, may create the robot and calls onRobotCreated, with additional trace logging and CI/setup adjustments.

Changes

Cohort / File(s) Summary
Build & Manifests
firmware/package.json, firmware/stackchan/manifest_wasm.json, firmware/stackchan/utilities/manifest_wasm_utility.json, firmware/stackchan/renderers-piu/manifest_wasm_renderer_piu.json
Adds build:wasm script and multiple WASM manifests configuring module mappings, preloads, fonts, memory/layout, runtime options, and relaxed tsconfig settings.
Startup & Mods
firmware/stackchan/main.ts, firmware/stackchan/default-mods/wasm/on-launch.ts
Adds WASM-specific startup branch with detailed tracing; evaluates defaultMod.onLaunch, conditionally creates robot and calls onRobotCreated; new onLaunch mod returns true.
WASM Drivers (stubs)
firmware/stackchan/drivers/wasm/driver-stub.ts, firmware/stackchan/drivers/wasm/dynamixel-driver.ts, firmware/stackchan/drivers/wasm/none-driver.ts, firmware/stackchan/drivers/wasm/rs30x-driver.ts, firmware/stackchan/drivers/wasm/scservo-driver.ts, firmware/stackchan/drivers/wasm/sg90-driver.ts
Introduces driver stub classes for multiple driver types; applyRotation/setTorque are no-ops and getRotation returns null/zero as stubs.
WASM Services
firmware/stackchan/services/wasm/network-service.ts, firmware/stackchan/services/wasm/preference-server.ts
Adds placeholder NetworkService and PreferenceServer with minimal lifecycle/connect APIs that invoke onConnected when provided.
WASM TTS implementations
firmware/stackchan/speeches/wasm/tts-elevenlabs.ts, firmware/stackchan/speeches/wasm/tts-local.ts, firmware/stackchan/speeches/wasm/tts-openai.ts, firmware/stackchan/speeches/wasm/tts-remote.ts, firmware/stackchan/speeches/wasm/tts-stub.ts, firmware/stackchan/speeches/wasm/tts-voicevox-web.ts, firmware/stackchan/speeches/wasm/tts-voicevox.ts
Adds multiple TTS stub classes exposing stream(text, volume?) with empty implementations.
WASM Hardware shims
firmware/stackchan/wasm/tone.ts, firmware/stackchan/wasm/led.ts, firmware/stackchan/wasm/microphone.ts
Adds stubbed Tone, Led, and Microphone classes with no-op methods used by the WASM runtime/simulator.
CI & Setup
.github/workflows/build.yml, .github/actions/setup/action.yml
Adds SDK pin/checkout step in CI and installs additional host packages (libglib2.0-dev, pkg-config) in the setup action to ensure required build tooling and reproducible SDK revision.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Main as Main
    participant Trace as Trace
    participant DefaultMod as DefaultMod
    participant Robot as Robot
    Main->>Trace: log("main start")
    Main->>Trace: log("install button bridge")
    Main->>Trace: log("wifi stage")
    Main->>DefaultMod: call onLaunch()
    DefaultMod-->>Main: true
    Main->>Trace: log("shouldRobotCreate = true")
    Main->>Robot: createRobot()
    Robot-->>Main: robotInstance
    Main->>Trace: log("calling onRobotCreated")
    Main->>DefaultMod: call onRobotCreated(robotInstance)
    DefaultMod-->>Main: (done)
    Main->>Trace: log("onRobotCreated completed")
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Suggested reviewers

  • stc1988

Poem

🐰 I stitched a tiny WASM trail,
Stubbed the motors, tunes, and tale,
Manifests set, simulator hums,
The build script hops and now it runs,
A rabbit cheers as testing comes.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title directly and specifically summarizes the main objective: adding WASM build support to Stack-chan firmware.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/wasm-stackchan-build

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 52e849fa60

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +4 to +5
async getRotation(): Promise<null> {
return null
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Return Maybe shape from WASM getRotation

Robot.updatePose assumes driver.getRotation() returns an object with a success field and immediately dereferences result.success. In the WASM path, this stub returns null, so the default WASM config (driver.type: "none" in manifest_wasm.json) will hit a runtime error as soon as pose updates run. This prevents normal simulator behavior and should return a valid Maybe<Rotation> object like the non-WASM NoneDriver implementation.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@firmware/stackchan/drivers/wasm/driver-stub.ts`:
- Around line 6-8: WasmDriver.getRotation() currently returns ZERO_ROTATION
directly which violates the driver result contract expected by callers to check
result.success; change getRotation to return a result object wrapping the
rotation, e.g. { success: true, rotation: ZERO_ROTATION } (or { success: false,
error: ... } on failure) so call sites that inspect result.success continue to
work; update the getRotation implementation and any related tests to return the
standardized result shape and reference ZERO_ROTATION inside that result.

In `@firmware/stackchan/drivers/wasm/dynamixel-driver.ts`:
- Around line 4-5: DynamixelDriver.getRotation currently returns null which
breaks callers expecting a Maybe object; change the function signature from
Promise<null> to Promise<Maybe<number>> (or the appropriate Maybe<T> for
rotation) and update the implementation to always return a Maybe shaped object
(e.g., { success: false } on error or { success: true, value: rotation } on
success) so callers can safely read result.success; ensure any internal paths
that previously returned null now return the correct Maybe form.

In `@firmware/stackchan/drivers/wasm/none-driver.ts`:
- Around line 4-5: getRotation currently returns null which breaks callers
expecting a Maybe-shaped object; change the implementation of getRotation to
return a Maybe-shaped object (e.g., an object containing the expected success
flag and value fields rather than null) so callers can safely read
result.success and result.value; update the returned Promise from getRotation to
resolve to that object shape (keeping the async signature) and ensure it matches
the project's Maybe type contract used elsewhere.

In `@firmware/stackchan/drivers/wasm/rs30x-driver.ts`:
- Around line 4-5: RS30XDriver.getRotation currently returns Promise<null>,
which breaks callers expecting a success-tagged result; update the method
signature and implementation in RS30XDriver.getRotation to return the expected
result shape (e.g., an object with a success boolean and either a rotation value
or an error message), ensure the Promise resolves to that object instead of
null, and adjust any internal logic to produce success: true with the rotation
data on success or success: false with an error on failure so downstream code
can reliably inspect the success tag.

In `@firmware/stackchan/drivers/wasm/scservo-driver.ts`:
- Around line 4-6: The getRotation() implementation currently returns null;
change it to return the zero-rotation object used by the existing WASM driver
baseline (not null). Update the method signature from Promise<null> to the
correct Rotation type (matching the other WASM driver code) and return the
zero-value rotation object (the same shape/fields the rest of the code expects)
so downstream callers no longer need null guards; adjust any imports/types if
necessary to reference the Rotation type and ensure the returned object matches
its shape.

In `@firmware/stackchan/drivers/wasm/sg90-driver.ts`:
- Around line 4-6: The getRotation method on PWMServoDriver currently returns
Promise<null> which violates the driver contract; change the signature to return
the correct rotation-shaped type (e.g., Promise<Rotation> or Promise<number> to
match other drivers) and return a real rotation value instead of null: implement
PWMServoDriver.getRotation to read the servo's current state (from an existing
field such as this.rotation/this.currentAngle or by converting pulse width /
calling the internal read method like getPulseWidth()/readPWMPosition()) and
return a Rotation-shaped object (or numeric angle) wrapped in a resolved Promise
so the method matches other driver implementations.

In `@firmware/stackchan/main.ts`:
- Around line 175-181: The onLaunch and onRobotCreated hooks are called
synchronously but may return Promises, so await their results to preserve
StackchanMod hook semantics and allow async errors to flow; change the call site
to await onLaunch() when computing shouldRobotCreate (e.g., const
shouldRobotCreate = await onLaunch?.() ?? true) and await
onRobotCreated?.(robot, globalEnv.device) after createRobot() (or make the
surrounding function async) so both hook results are handled correctly and any
thrown/rejected errors are propagated.

In `@firmware/stackchan/manifest_wasm.json`:
- Around line 15-19: The modules["*"] array in manifest_wasm.json is
mis-formatted for Biome; reformat the value of modules["*"] to match the
repository's JSON formatting style (one string per line, properly indented, with
commas placed/omitted as in the rest of the file) so the file passes the Biome
formatter; update the array entries for "./touch", "./robot", and "./main"
accordingly and run the Biome formatter (or commit the exact formatting used
elsewhere in the file) before pushing.

In `@firmware/stackchan/speeches/wasm/tts-stub.ts`:
- Around line 2-3: The constructor and stream method in this WASM TTS stub use
untyped parameters; change their signatures to match the explicit types used in
the other WASM TTS implementations (e.g., tts-voicevox.ts, tts-elevenlabs.ts,
tts-remote.ts, tts-openai.ts, tts-local.ts). Specifically, update
constructor(_options) to constructor(options: /* same options type used by the
other WASM TTS classes */) and update async stream(_text, _volume) to async
stream(text: string, volume?: number): /* same return type as other WASM TTS
stream methods */ so the parameter names, types, and return type match the rest
of the implementations.

In `@firmware/stackchan/wasm/microphone.ts`:
- Line 3: Restore the original optional parameter on the microphone API by
changing the record function signature back to record(durationMilliSec?:
number): Promise<ArrayBuffer> (i.e., accept an optional durationMilliSec
parameter), keep existing internal WASM logic unchanged but consume or ignore
durationMilliSec as appropriate (e.g., pass it into the WASM call or use it to
set a timeout/default), and ensure callers remain compatible with non-WASM code
paths; update the record method declaration in microphone.ts accordingly so
TypeScript call-sites don’t break.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7a752a29-2d01-41ed-998e-8b788a080ea3

📥 Commits

Reviewing files that changed from the base of the PR and between caca377 and 52e849f.

📒 Files selected for processing (24)
  • firmware/package.json
  • firmware/stackchan/default-mods/wasm/on-launch.ts
  • firmware/stackchan/drivers/wasm/driver-stub.ts
  • firmware/stackchan/drivers/wasm/dynamixel-driver.ts
  • firmware/stackchan/drivers/wasm/none-driver.ts
  • firmware/stackchan/drivers/wasm/rs30x-driver.ts
  • firmware/stackchan/drivers/wasm/scservo-driver.ts
  • firmware/stackchan/drivers/wasm/sg90-driver.ts
  • firmware/stackchan/main.ts
  • firmware/stackchan/manifest_wasm.json
  • firmware/stackchan/renderers-piu/manifest_wasm_renderer_piu.json
  • firmware/stackchan/services/wasm/network-service.ts
  • firmware/stackchan/services/wasm/preference-server.ts
  • firmware/stackchan/speeches/wasm/tts-elevenlabs.ts
  • firmware/stackchan/speeches/wasm/tts-local.ts
  • firmware/stackchan/speeches/wasm/tts-openai.ts
  • firmware/stackchan/speeches/wasm/tts-remote.ts
  • firmware/stackchan/speeches/wasm/tts-stub.ts
  • firmware/stackchan/speeches/wasm/tts-voicevox-web.ts
  • firmware/stackchan/speeches/wasm/tts-voicevox.ts
  • firmware/stackchan/utilities/manifest_wasm_utility.json
  • firmware/stackchan/wasm/led.ts
  • firmware/stackchan/wasm/microphone.ts
  • firmware/stackchan/wasm/tone.ts

Comment on lines +6 to +8
async getRotation() {
return ZERO_ROTATION
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Align WasmDriver.getRotation() with the driver result contract.

At Line 7, returning a plain rotation object (without success) is unsafe for existing call sites that branch on result.success.

Proposed fix
 class WasmDriver {
   constructor(_options) {}
   async applyRotation(_rotation, _time) {}
   async getRotation() {
-    return ZERO_ROTATION
+    return {
+      success: true,
+      value: { ...ZERO_ROTATION },
+    }
   }
   async setTorque(_torque) {}
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async getRotation() {
return ZERO_ROTATION
}
async getRotation() {
return {
success: true,
value: { ...ZERO_ROTATION },
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@firmware/stackchan/drivers/wasm/driver-stub.ts` around lines 6 - 8,
WasmDriver.getRotation() currently returns ZERO_ROTATION directly which violates
the driver result contract expected by callers to check result.success; change
getRotation to return a result object wrapping the rotation, e.g. { success:
true, rotation: ZERO_ROTATION } (or { success: false, error: ... } on failure)
so call sites that inspect result.success continue to work; update the
getRotation implementation and any related tests to return the standardized
result shape and reference ZERO_ROTATION inside that result.

Comment on lines +4 to +5
async getRotation(): Promise<null> {
return null
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

DynamixelDriver.getRotation() should return a Maybe object, not null.

At Line 4, the null return can trigger runtime exceptions where result.success is accessed.

Proposed fix
 export class DynamixelDriver {
   constructor(_options?: unknown) {}
   async applyRotation(_rotation: unknown, _time?: number): Promise<void> {}
-  async getRotation(): Promise<null> {
-    return null
+  async getRotation(): Promise<{ success: false }> {
+    return { success: false }
   }
   async setTorque(_torque: boolean): Promise<void> {}
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async getRotation(): Promise<null> {
return null
async getRotation(): Promise<{ success: false }> {
return { success: false }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@firmware/stackchan/drivers/wasm/dynamixel-driver.ts` around lines 4 - 5,
DynamixelDriver.getRotation currently returns null which breaks callers
expecting a Maybe object; change the function signature from Promise<null> to
Promise<Maybe<number>> (or the appropriate Maybe<T> for rotation) and update the
implementation to always return a Maybe shaped object (e.g., { success: false }
on error or { success: true, value: rotation } on success) so callers can safely
read result.success; ensure any internal paths that previously returned null now
return the correct Maybe form.

Comment on lines +4 to +5
async getRotation(): Promise<null> {
return null
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Return a Maybe-shaped object instead of null from getRotation().

At Line 4, returning null breaks callers that read result.success, causing runtime failures in the robot update path.

Proposed fix
 export class NoneDriver {
   constructor(_options?: unknown) {}
   async applyRotation(_rotation: unknown, _time?: number): Promise<void> {}
-  async getRotation(): Promise<null> {
-    return null
+  async getRotation(): Promise<{ success: false }> {
+    return { success: false }
   }
   async setTorque(_torque: boolean): Promise<void> {}
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async getRotation(): Promise<null> {
return null
export class NoneDriver {
constructor(_options?: unknown) {}
async applyRotation(_rotation: unknown, _time?: number): Promise<void> {}
async getRotation(): Promise<{ success: false }> {
return { success: false }
}
async setTorque(_torque: boolean): Promise<void> {}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@firmware/stackchan/drivers/wasm/none-driver.ts` around lines 4 - 5,
getRotation currently returns null which breaks callers expecting a Maybe-shaped
object; change the implementation of getRotation to return a Maybe-shaped object
(e.g., an object containing the expected success flag and value fields rather
than null) so callers can safely read result.success and result.value; update
the returned Promise from getRotation to resolve to that object shape (keeping
the async signature) and ensure it matches the project's Maybe type contract
used elsewhere.

Comment on lines +4 to +5
async getRotation(): Promise<null> {
return null
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

RS30XDriver.getRotation() should not return null.

At Line 4, Promise<null> is incompatible with the runtime expectation of a success-tagged result object and can crash downstream logic.

Proposed fix
 export class RS30XDriver {
   constructor(_options?: unknown) {}
   async applyRotation(_rotation: unknown, _time?: number): Promise<void> {}
-  async getRotation(): Promise<null> {
-    return null
+  async getRotation(): Promise<{ success: false }> {
+    return { success: false }
   }
   async setTorque(_torque: boolean): Promise<void> {}
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async getRotation(): Promise<null> {
return null
async getRotation(): Promise<{ success: false }> {
return { success: false }
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@firmware/stackchan/drivers/wasm/rs30x-driver.ts` around lines 4 - 5,
RS30XDriver.getRotation currently returns Promise<null>, which breaks callers
expecting a success-tagged result; update the method signature and
implementation in RS30XDriver.getRotation to return the expected result shape
(e.g., an object with a success boolean and either a rotation value or an error
message), ensure the Promise resolves to that object instead of null, and adjust
any internal logic to produce success: true with the rotation data on success or
success: false with an error on failure so downstream code can reliably inspect
the success tag.

Comment on lines +4 to +6
async getRotation(): Promise<null> {
return null
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

getRotation() should not return null in this WASM driver.

Line 4 diverges from the existing WASM driver baseline (zero rotation object), and null increases downstream runtime guard burden.

Suggested fix
+const ZERO_ROTATION = { y: 0, p: 0, r: 0 } as const
+
 export class SCServoDriver {
   constructor(_options?: unknown) {}
   async applyRotation(_rotation: unknown, _time?: number): Promise<void> {}
-  async getRotation(): Promise<null> {
-    return null
+  async getRotation(): Promise<typeof ZERO_ROTATION> {
+    return ZERO_ROTATION
   }
   async setTorque(_torque: boolean): Promise<void> {}
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@firmware/stackchan/drivers/wasm/scservo-driver.ts` around lines 4 - 6, The
getRotation() implementation currently returns null; change it to return the
zero-rotation object used by the existing WASM driver baseline (not null).
Update the method signature from Promise<null> to the correct Rotation type
(matching the other WASM driver code) and return the zero-value rotation object
(the same shape/fields the rest of the code expects) so downstream callers no
longer need null guards; adjust any imports/types if necessary to reference the
Rotation type and ensure the returned object matches its shape.

Comment on lines +4 to +6
async getRotation(): Promise<null> {
return null
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

PWMServoDriver.getRotation() should return a rotation-shaped value, not null.

Line 4 is contract-inconsistent with the other driver implementations/stubs and can cause downstream runtime errors.

Suggested fix
+const ZERO_ROTATION = { y: 0, p: 0, r: 0 } as const
+
 export class PWMServoDriver {
   constructor(_options?: unknown) {}
   async applyRotation(_rotation: unknown, _time?: number): Promise<void> {}
-  async getRotation(): Promise<null> {
-    return null
+  async getRotation(): Promise<typeof ZERO_ROTATION> {
+    return ZERO_ROTATION
   }
   async setTorque(_torque: boolean): Promise<void> {}
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async getRotation(): Promise<null> {
return null
}
const ZERO_ROTATION = { y: 0, p: 0, r: 0 } as const
export class PWMServoDriver {
constructor(_options?: unknown) {}
async applyRotation(_rotation: unknown, _time?: number): Promise<void> {}
async getRotation(): Promise<typeof ZERO_ROTATION> {
return ZERO_ROTATION
}
async setTorque(_torque: boolean): Promise<void> {}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@firmware/stackchan/drivers/wasm/sg90-driver.ts` around lines 4 - 6, The
getRotation method on PWMServoDriver currently returns Promise<null> which
violates the driver contract; change the signature to return the correct
rotation-shaped type (e.g., Promise<Rotation> or Promise<number> to match other
drivers) and return a real rotation value instead of null: implement
PWMServoDriver.getRotation to read the servo's current state (from an existing
field such as this.rotation/this.currentAngle or by converting pulse width /
calling the internal read method like getPulseWidth()/readPWMPosition()) and
return a Rotation-shaped object (or numeric angle) wrapped in a resolved Promise
so the method matches other driver implementations.

Comment on lines +175 to +181
const shouldRobotCreate = onLaunch?.() ?? true
trace(`[main] onLaunch shouldRobotCreate=${shouldRobotCreate}\n`)
if (shouldRobotCreate) {
const robot = createRobot()
trace('[main] robot created\n')
onRobotCreated?.(robot, globalEnv.device)
trace('[main] onRobotCreated complete\n')
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Await WASM hook calls to preserve StackchanMod hook semantics.

At Line 175 and Line 180, hooks are called synchronously. If either hook returns a Promise, shouldRobotCreate is mis-evaluated and async errors bypass this flow.

Proposed fix
   if (config.wasm) {
     trace('[main] WASM synchronous launch path\n')
     const { onRobotCreated, onLaunch } = defaultMod
-    const shouldRobotCreate = onLaunch?.() ?? true
+    const shouldRobotCreate = await (onLaunch?.() ?? true)
     trace(`[main] onLaunch shouldRobotCreate=${shouldRobotCreate}\n`)
     if (shouldRobotCreate) {
       const robot = createRobot()
       trace('[main] robot created\n')
-      onRobotCreated?.(robot, globalEnv.device)
+      await onRobotCreated?.(robot, globalEnv.device)
       trace('[main] onRobotCreated complete\n')
     }
     return
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const shouldRobotCreate = onLaunch?.() ?? true
trace(`[main] onLaunch shouldRobotCreate=${shouldRobotCreate}\n`)
if (shouldRobotCreate) {
const robot = createRobot()
trace('[main] robot created\n')
onRobotCreated?.(robot, globalEnv.device)
trace('[main] onRobotCreated complete\n')
const shouldRobotCreate = await (onLaunch?.() ?? true)
trace(`[main] onLaunch shouldRobotCreate=${shouldRobotCreate}\n`)
if (shouldRobotCreate) {
const robot = createRobot()
trace('[main] robot created\n')
await onRobotCreated?.(robot, globalEnv.device)
trace('[main] onRobotCreated complete\n')
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@firmware/stackchan/main.ts` around lines 175 - 181, The onLaunch and
onRobotCreated hooks are called synchronously but may return Promises, so await
their results to preserve StackchanMod hook semantics and allow async errors to
flow; change the call site to await onLaunch() when computing shouldRobotCreate
(e.g., const shouldRobotCreate = await onLaunch?.() ?? true) and await
onRobotCreated?.(robot, globalEnv.device) after createRobot() (or make the
surrounding function async) so both hook results are handled correctly and any
thrown/rejected errors are propagated.

Comment thread firmware/stackchan/manifest_wasm.json Outdated
Comment on lines +15 to +19
"*": [
"./touch",
"./robot",
"./main"
],
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix Biome formatting for modules["*"] to unblock CI.

Line 15-Line 19 currently violates the formatter expectation reported by the pipeline.

Proposed fix
-    "*": [
-      "./touch",
-      "./robot",
-      "./main"
-    ],
+    "*": ["./touch", "./robot", "./main"],
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"*": [
"./touch",
"./robot",
"./main"
],
"*": ["./touch", "./robot", "./main"],
🧰 Tools
🪛 GitHub Actions: Build Stack-chan Firmware

[error] 13-19: Biome format failed for stackchan/manifest_wasm.json. Formatter would have changed "modules" entry for "" from a multi-line array to a single-line array: "": ["./touch", "./robot", "./main"].

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@firmware/stackchan/manifest_wasm.json` around lines 15 - 19, The modules["*"]
array in manifest_wasm.json is mis-formatted for Biome; reformat the value of
modules["*"] to match the repository's JSON formatting style (one string per
line, properly indented, with commas placed/omitted as in the rest of the file)
so the file passes the Biome formatter; update the array entries for "./touch",
"./robot", and "./main" accordingly and run the Biome formatter (or commit the
exact formatting used elsewhere in the file) before pushing.

Comment on lines +2 to +3
constructor(_options) {}
async stream(_text, _volume) {}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether strict/noImplicitAny is enabled and confirm current untyped params.
fd -a 'tsconfig*.json' firmware --exec sh -c 'echo "=== $1 ==="; sed -n "1,220p" "$1"' sh {}
rg -n --no-heading 'constructor\(_options\)|stream\(_text, _volume\)' firmware/stackchan/speeches/wasm/tts-stub.ts

Repository: stack-chan/stack-chan

Length of output: 4389


🏁 Script executed:

# Find other WASM TTS stub or similar files to check parameter typing patterns
fd -type f 'tts.*\.ts' firmware/stackchan/speeches/wasm/ | head -20
rg -l 'class TTS|export class.*TTS' firmware/stackchan/speeches/ -A 3 | head -20

Repository: stack-chan/stack-chan

Length of output: 855


🏁 Script executed:

# Check parameter signatures in the WASM TTS files
for file in firmware/stackchan/speeches/wasm/tts-{stub,voicevox,elevenlabs,remote,openai,local}.ts; do
  if [ -f "$file" ]; then
    echo "=== $file ==="
    rg -A 1 'constructor|stream.*\(' "$file" | head -20
  fi
done

Repository: stack-chan/stack-chan

Length of output: 986


Match parameter types with other WASM TTS implementations in this PR.

At Line 2 and Line 3, the untyped parameters are inconsistent with all other WASM TTS implementations (tts-voicevox.ts, tts-elevenlabs.ts, tts-remote.ts, tts-openai.ts, tts-local.ts), which use explicit types.

💡 Proposed fix
 export class TTS {
-  constructor(_options) {}
-  async stream(_text, _volume) {}
+  constructor(_options?: unknown) {}
+  async stream(_text: string, _volume?: number): Promise<void> {}
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
constructor(_options) {}
async stream(_text, _volume) {}
export class TTS {
constructor(_options?: unknown) {}
async stream(_text: string, _volume?: number): Promise<void> {}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@firmware/stackchan/speeches/wasm/tts-stub.ts` around lines 2 - 3, The
constructor and stream method in this WASM TTS stub use untyped parameters;
change their signatures to match the explicit types used in the other WASM TTS
implementations (e.g., tts-voicevox.ts, tts-elevenlabs.ts, tts-remote.ts,
tts-openai.ts, tts-local.ts). Specifically, update constructor(_options) to
constructor(options: /* same options type used by the other WASM TTS classes */)
and update async stream(_text, _volume) to async stream(text: string, volume?:
number): /* same return type as other WASM TTS stream methods */ so the
parameter names, types, and return type match the rest of the implementations.

@@ -0,0 +1,7 @@
export default class Microphone {
constructor(_options?: unknown) {}
async record(): Promise<ArrayBuffer> {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Preserve record(durationMilliSec?) API compatibility.

Line 3 drops the optional duration parameter used by the non-WASM microphone API. This can cause TS call-site mismatch in shared code paths.

Suggested fix
-  async record(): Promise<ArrayBuffer> {
+  async record(_durationMilliSec = 3000): Promise<ArrayBuffer> {
     return new ArrayBuffer(0)
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async record(): Promise<ArrayBuffer> {
async record(_durationMilliSec = 3000): Promise<ArrayBuffer> {
return new ArrayBuffer(0)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@firmware/stackchan/wasm/microphone.ts` at line 3, Restore the original
optional parameter on the microphone API by changing the record function
signature back to record(durationMilliSec?: number): Promise<ArrayBuffer> (i.e.,
accept an optional durationMilliSec parameter), keep existing internal WASM
logic unchanged but consume or ignore durationMilliSec as appropriate (e.g.,
pass it into the WASM call or use it to set a timeout/default), and ensure
callers remain compatible with non-WASM code paths; update the record method
declaration in microphone.ts accordingly so TypeScript call-sites don’t break.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant