Skip to content

step 3/6: chat UIホットパス最適化#365

Merged
meganetaaan merged 32 commits intodev/v1.0from
step-pr/03-chat-ui-hotpath
Apr 23, 2026
Merged

step 3/6: chat UIホットパス最適化#365
meganetaaan merged 32 commits intodev/v1.0from
step-pr/03-chat-ui-hotpath

Conversation

@meganetaaan
Copy link
Copy Markdown
Collaborator

@meganetaaan meganetaaan commented Feb 28, 2026

Summary

  • Optimize transcript balloon update path
  • Optimize mouth-open update hot path

Scope

  • Base: dev/v1.0
  • Head: step-pr/03-chat-ui-hotpath
  • This is step 3 of 6.

Notes

Summary by CodeRabbit

  • New Features

    • New "image" face mode selectable alongside existing faces
    • Chat status bar added to the app UI
    • New app-bar font resource included
  • Improvements

    • Speech balloon rebuilt for wrapped multi-line text with dynamic sizing and padding
    • Face state rehydration preserves positioning and palette across lifecycle events
    • Renderer and startup flows tightened for more robust chat initialization
  • Documentation

    • Chat configuration docs updated: identifier renamed from "specifier" to "type"
  • Tests

    • New and updated tests for chat config validation and face behaviors

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 28, 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

Renamed ChatConfig identifier from specifier to type across docs, runtime, tests, and manifests; startup now snapshots and validates config.chat.type; added image face mode and ImageFace renderer; redesigned speech balloon; added face rehydration/caching, ChatStatusBar, shape template helper, tests, and manifest/resource updates.

Changes

Cohort / File(s) Summary
Chat config & startup
firmware/chat.md, firmware/mods/chat_audioio/mod.js, firmware/stackchan/services/chat.ts, firmware/tests/chats/chat-service/main.ts, firmware/tests/chats/chat-audioio-config/main.ts, firmware/tests/chats/chat-audioio-config/manifest.json, firmware/stackchan/manifest_local.json
Renamed specifiertype in types/docs/tests; startup snapshots config.chat, defaults instructions, validates config.chat.type (non-empty string) and disables chat on invalid values; added tests/manifests for chat.type validation.
Face mode & renderer registration
firmware/stackchan/default-mods/on-robot-created.ts, firmware/stackchan/main.ts, firmware/tests/renderers/piu-simple/main.ts
Added 'image' face mode and ImageFace renderer; toggle cycles simple → dog → image → simple; initialization/sync helper introduced; tests updated.
ChatStatusBar integration & CommonView
firmware/stackchan/renderers-piu/renderer-dog.ts, firmware/stackchan/renderers-piu/renderer-simple.ts, firmware/stackchan/renderers-piu/renderer-small.ts, firmware/stackchan/renderers-piu/common-view.ts
Injected ChatStatusBar as appBar into Application constructors; extended CommonView types/logic to accept/canonicalize appBar.
Speech balloon redesign
firmware/stackchan/renderers-piu/effects/speech-balloon.ts, firmware/tests/chats/chat-balloon/main.ts
Replaced outline+scrolling label with textured background + Text; added wrapping, dynamic height, minHeight/paddingX/paddingY, public setText/clear; tests updated for theme and rebuild-on-resize.
Face behavior & view enhancements
firmware/stackchan/renderers-piu/behaviors/face.ts, firmware/stackchan/renderers-piu/face-view.ts, firmware/tests/renderers/face-view-state/main.ts
Added getBaseCoordinates, rehydrate, onFaceSkin; implemented cached face context, palette-change detection, rehydrateFace/applyFaceState; setFace preserves coordinates and reapplies cached state; tests added.
Shape/template typing & factory
firmware/stackchan/renderers-piu/template.ts, firmware/stackchan/renderers-piu/parts/*, firmware/stackchan/renderers-piu/effects/emoticon.ts
Added ShapeTemplate type and defineShapeTemplate helper; replaced several Shape.template(...) usages with defineShapeTemplate(...); tightened PiuShape typings (omit/reintroduce outline props).
Type/import alignment (multirow, parts)
firmware/stackchan/renderers-piu/effects/multirow-balloon.ts, firmware/stackchan/renderers-piu/parts/*
Reordered type imports and aligned WithShape/WithSkin types to Omit<PiuShape,...> patterns; no runtime behavior changes.
Renderer tests, resources & manifests
firmware/stackchan/manifest.json, firmware/tests/renderers/piu-simple/manifest.json, firmware/tests/renderers/piu-simple/main.ts, firmware/tests/renderers/default-mod-face/*, firmware/tests/renderers/face-view-state/*, firmware/tsconfig.json
Added *-alpha monochrome font resources; added/updated test manifests and entrypoints; set touchIntervalMs for a platform; added mocks/* TS path alias.
Minor wiring & imports
various renderer files (renderer-dog.ts, renderer-simple.ts, renderer-small.ts, small mod files)
Import/constructor wiring adjustments (factory lambdas, globalEnv wrapper, SimButton typing) and ordering; no public API signature changes.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    rect rgba(220,230,241,0.5)
    participant Robot
    end
    rect rgba(200,240,220,0.5)
    participant Startup
    end
    rect rgba(255,230,200,0.5)
    participant ChatService
    end
    rect rgba(240,220,255,0.5)
    participant ChatAudioIO
    end

    Robot->>Startup: onRobotCreated(robot)
    Startup->>Startup: rawChatConfig = config.chat || {}
    Startup->>Startup: chatConfig = { ...rawChatConfig, instructions: ... }
    Startup->>Startup: validate typeof chatConfig.type === 'string' && chatConfig.type !== ''
    alt valid type
        Startup->>ChatService: construct(config.type, ...)
        ChatService->>ChatAudioIO: ChatAudioIOCtor(specifier: config.type)
        ChatAudioIO-->>ChatService: initialized events/state
        ChatService-->>Startup: chat enabled
    else invalid type
        Startup-->>Robot: trace invalid config.chat.type (example)
        Startup-->>Startup: exit early (chat disabled)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • remove local config #353: Edits to manifest_local.json top-level config fields that overlap with added tts/chat/driver entries.

Poem

🐰 I hopped through configs, specifier to type,
Three faces now wobble and take the mic,
Balloons found textures and wrapped words just right,
A chat bar arrived to gleam in the light,
Cached faces hum softly as day turns to night.

🚥 Pre-merge checks | ✅ 2 | ❌ 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 (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'step 3/6: chat UIホットパス最適化' directly aligns with the objectives, which focus on optimizing the chat UI hot path (step 3 of a multi-step series).

✏️ 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 step-pr/03-chat-ui-hotpath

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: db759121f8

ℹ️ 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 thread firmware/mods/chat_audioio/mod.js Outdated
Comment thread firmware/mods/chat_audioio/mod.js Outdated
@meganetaaan meganetaaan force-pushed the step-pr/02-chat-foundation branch from 0efe339 to e138186 Compare March 11, 2026 23:34
@meganetaaan meganetaaan force-pushed the step-pr/03-chat-ui-hotpath branch from 4061491 to b6843ad Compare March 11, 2026 23:34
@meganetaaan
Copy link
Copy Markdown
Collaborator Author

別issueで修正する: #373

@meganetaaan meganetaaan reopened this Mar 14, 2026
@meganetaaan meganetaaan changed the base branch from step-pr/02-chat-foundation to step-pr/02-chat-foundation-dev-v1.0 April 4, 2026 10:52
@meganetaaan meganetaaan changed the base branch from step-pr/02-chat-foundation-dev-v1.0 to dev/v1.0 April 4, 2026 11:03
…k-dev-v1

# Conflicts:
#	firmware/stackchan/manifest_local.json
#	firmware/stackchan/renderers-piu/behaviors/face.ts
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: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
firmware/chat.md (2)

118-130: ⚠️ Potential issue | 🟡 Minor

Keep providerID in the documented config surface.

ChatConfig still exposes providerID, but these sections now only describe type/modelID/voiceID/instructions. That leaves the public contract and the config test plan out of sync.

Also applies to: 278-285, 390-395

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

In `@firmware/chat.md` around lines 118 - 130, The documentation removed
providerID from the `config.chat` surface but the code still exposes
`ChatConfig.providerID`; update the docs and example to include `providerID`
(e.g., in the example under `config.chat` and the descriptive text) and note
that `ChatService` must forward `providerID` into the ChatAudioIO `specifier`;
ensure references to `config.chat` (including the example block and the sections
around lines ~278-285 and ~390-395) mention `providerID` alongside
`type/modelID/voiceID/instructions` so the public contract and test plan match
the `ChatConfig` shape.

121-130: ⚠️ Potential issue | 🟡 Minor

Add a language to this fenced example.

This now trips markdownlint MD040.

📝 Suggested fix
-```
+```ts
 config: {
   chat: {
     type: "openAIRealtime",
     modelID: "gpt-realtime-mini",
     voiceID: "marin",
     instructions: "...",
   }
 }
-```
+```
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@firmware/chat.md` around lines 121 - 130, The fenced code block example for
the config object is missing a language tag, which triggers markdownlint MD040;
update the opening triple-backtick fence that precedes the config example (the
block showing config: { chat: { type: "openAIRealtime", modelID:
"gpt-realtime-mini", voiceID: "marin", instructions: "..." } } ) to include a
language identifier such as "ts" (i.e., change ``` to ```ts) so the block is
properly typed and linter-compliant.
🧹 Nitpick comments (2)
firmware/stackchan/renderers-piu/effects/speech-balloon.ts (1)

91-117: The dynamic-height path is still quadratic.

When height is not fixed, setText() re-runs countWrappedLines(), which concatenates and measures a growing prefix for every character. For streamed transcripts, that makes update cost grow with the whole accumulated string again. Consider caching wrap state or line widths instead of remeasuring every prefix on each update.

Also applies to: 172-178

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

In `@firmware/stackchan/renderers-piu/effects/speech-balloon.ts` around lines 91 -
117, countWrappedLines currently rebuilds and remeasures every growing prefix on
each call (used by resolveHeight), producing quadratic cost for
streaming/appended text; change to an incremental/cached wrap algorithm:
maintain persistent wrap state (e.g., array of lineWidths or current lines)
keyed to the existing text and update it when setText/appends occur rather than
recomputing from scratch, and have countWrappedLines (or its replacement) use
that cached state to compute the new line count in O(chars appeneded) time;
locate and update the logic in countWrappedLines and resolveHeight and any other
place calling the function (the streaming update path referenced around the
second occurrence) to use the new cached structure and update it on each append
instead of remeasuring all prefixes.
firmware/stackchan/manifest_local.json (1)

4-8: Avoid checking a workstation-specific Voicevox endpoint into the shared local manifest.

192.168.7.136 will be dead for almost everyone else pulling the branch. Prefer leaving tts.host/tts.port to mcconfig overrides or an untracked local overlay.

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

In `@firmware/stackchan/manifest_local.json` around lines 4 - 8, The
manifest_local.json currently hardcodes a workstation-specific Voicevox endpoint
(tts.host = "192.168.7.136" and tts.port = 50021); remove these hardcoded values
from the tracked manifest and make tts.host and tts.port configurable via the
existing mcconfig override or an untracked local overlay instead (leave them
null/empty or delete the keys in manifest_local.json), ensuring consumers can
set values in their local mcconfig or overlay; reference the tts.host and
tts.port fields and update any README or comments to instruct developers to set
these in mcconfig/local overlay rather than committing workstation-specific
endpoints.
🤖 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/mods/chat_audioio/mod.js`:
- Around line 23-26: The guard should ensure config.chat.type is a string, not
just truthy: change the check around chatConfig (used where you call trace and
later derive the specifier) to verify typeof chatConfig?.type === 'string' and
treat any non-string (including objects, numbers, null) the same as missing by
logging via trace that config.chat.type must be a string and disabling chat;
update the trace message to mention the type requirement so downstream code that
constructs the specifier (from config.chat.type) never receives a non-string
value.

In `@firmware/stackchan/default-mods/on-robot-created.ts`:
- Around line 37-50: The code only emits 'onFaceMode' inside the toggle
callback, so listeners never get the initial 'simple' mode; after registering
the drawer button (the robot.application.addDrawerButton call) emit the same
event for the initial state by invoking the same distribute call with faceMode
(e.g. call robot.application.distribute?.('onFaceMode', faceMode) or, if you
must target the renderer like in the callback, grab the current renderer
application and call app?.distribute?.('onFaceMode', faceMode)); also ensure
robot.application.setDrawerButtonState('toggleFace', faceMode !== 'simple') is
set for the initial state so UI and listeners are synchronized.

In `@firmware/stackchan/renderers-piu/effects/speech-balloon.ts`:
- Around line 124-145: ensureParts currently recreates background/bodyText but
calls this.updatePalette(defaultFaceContext) which can leave new nodes
unskinned; store the last active face context in a field (e.g.,
this._activeFaceContext) when onFaceContext runs, and replace uses of
defaultFaceContext in ensureParts (and the other rebuild/repaint sites that call
updatePalette) with this._activeFaceContext so newly created background and
bodyText are re-skinned immediately; ensure onFaceContext sets
this._activeFaceContext and calls this.updatePalette(this._activeFaceContext),
and keep calling this.updateText(self, currentText) after reapplying the active
palette.

In `@firmware/stackchan/renderers-piu/face-view.ts`:
- Around line 27-30: The face behavior cache isn't updated when reapplying
state; add a behavior-level rehydrate hook (e.g., FaceBehavior.rehydrate or
FaceContainerBehavior.rehydrate) that accepts the container and new
FaceContext/palette and updates the cached context/palette inside FaceBehavior,
then call this hook from onFaceSkin() and applyFaceState() immediately after
pushing state into the subtree so the FaceBehavior cache is updated in lockstep
with descendants; ensure the FaceBehavior implementation updates its internal
cached context/palette in the new rehydrate method so onDisplaying()/resume()
won't use stale values.

---

Outside diff comments:
In `@firmware/chat.md`:
- Around line 118-130: The documentation removed providerID from the
`config.chat` surface but the code still exposes `ChatConfig.providerID`; update
the docs and example to include `providerID` (e.g., in the example under
`config.chat` and the descriptive text) and note that `ChatService` must forward
`providerID` into the ChatAudioIO `specifier`; ensure references to
`config.chat` (including the example block and the sections around lines
~278-285 and ~390-395) mention `providerID` alongside
`type/modelID/voiceID/instructions` so the public contract and test plan match
the `ChatConfig` shape.
- Around line 121-130: The fenced code block example for the config object is
missing a language tag, which triggers markdownlint MD040; update the opening
triple-backtick fence that precedes the config example (the block showing
config: { chat: { type: "openAIRealtime", modelID: "gpt-realtime-mini", voiceID:
"marin", instructions: "..." } } ) to include a language identifier such as "ts"
(i.e., change ``` to ```ts) so the block is properly typed and linter-compliant.

---

Nitpick comments:
In `@firmware/stackchan/manifest_local.json`:
- Around line 4-8: The manifest_local.json currently hardcodes a
workstation-specific Voicevox endpoint (tts.host = "192.168.7.136" and tts.port
= 50021); remove these hardcoded values from the tracked manifest and make
tts.host and tts.port configurable via the existing mcconfig override or an
untracked local overlay instead (leave them null/empty or delete the keys in
manifest_local.json), ensuring consumers can set values in their local mcconfig
or overlay; reference the tts.host and tts.port fields and update any README or
comments to instruct developers to set these in mcconfig/local overlay rather
than committing workstation-specific endpoints.

In `@firmware/stackchan/renderers-piu/effects/speech-balloon.ts`:
- Around line 91-117: countWrappedLines currently rebuilds and remeasures every
growing prefix on each call (used by resolveHeight), producing quadratic cost
for streaming/appended text; change to an incremental/cached wrap algorithm:
maintain persistent wrap state (e.g., array of lineWidths or current lines)
keyed to the existing text and update it when setText/appends occur rather than
recomputing from scratch, and have countWrappedLines (or its replacement) use
that cached state to compute the new line count in O(chars appeneded) time;
locate and update the logic in countWrappedLines and resolveHeight and any other
place calling the function (the streaming update path referenced around the
second occurrence) to use the new cached structure and update it on each append
instead of remeasuring all prefixes.
🪄 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: c4b5bb24-187c-4a1e-9c84-e65fd8533860

📥 Commits

Reviewing files that changed from the base of the PR and between 1993afc and 53996bb.

📒 Files selected for processing (18)
  • firmware/chat.md
  • firmware/mods/chat_audioio/mod.js
  • firmware/stackchan/default-mods/on-robot-created.ts
  • firmware/stackchan/main.ts
  • firmware/stackchan/manifest.json
  • firmware/stackchan/manifest_local.json
  • firmware/stackchan/renderers-piu/behaviors/face.ts
  • firmware/stackchan/renderers-piu/common-view.ts
  • firmware/stackchan/renderers-piu/effects/speech-balloon.ts
  • firmware/stackchan/renderers-piu/face-view.ts
  • firmware/stackchan/renderers-piu/renderer-dog.ts
  • firmware/stackchan/renderers-piu/renderer-simple.ts
  • firmware/stackchan/renderers-piu/renderer-small.ts
  • firmware/stackchan/services/chat.ts
  • firmware/tests/chats/chat-service/main.ts
  • firmware/tests/renderers/piu-simple/main.ts
  • firmware/tests/renderers/piu-simple/manifest.json
  • firmware/tsconfig.json

Comment thread firmware/mods/chat_audioio/mod.js Outdated
Comment thread firmware/stackchan/default-mods/on-robot-created.ts Outdated
Comment thread firmware/stackchan/renderers-piu/effects/speech-balloon.ts
Comment thread firmware/stackchan/renderers-piu/face-view.ts
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: 2

🧹 Nitpick comments (1)
firmware/tests/renderers/default-mod-face/main.ts (1)

6-12: Exercise the registered drawer callback as well.

The test validates initial sync, but it would still pass if the toggleFace callback stopped cycling simple → dog → image → simple. Capturing kind, initialState, and invoking the callback would cover the actual drawer interaction added in firmware/stackchan/default-mods/on-robot-created.ts:46-55.

Suggested test expansion
+type DrawerButton = {
+  key?: string
+  kind?: string
+  initialState?: boolean
+  callback?: (target: {
+    renderer?: {
+      setFace?: (face: unknown) => void
+      application?: { distribute?: (event: string, payload: unknown) => void }
+    }
+  }) => void
+}
+
-const buttons: { key?: string }[] = []
+const buttons: DrawerButton[] = []
 const drawerStates: [string, boolean][] = []
 const events: [string, unknown][] = []

 const robot = {
   application: {
-    addDrawerButton: (button: { key?: string }) => {
+    addDrawerButton: (button: DrawerButton) => {
       buttons.push(button)
     },
     setDrawerButtonState: (key: string, active: boolean) => {
       drawerStates.push([key, active])
@@
 onRobotCreated(robot as never)

 equal(buttons[0]?.key, 'toggleFace', 'toggleFace button should be registered')
+equal(buttons[0]?.kind, 'toggle', 'toggleFace should be registered as a toggle button')
+equal(buttons[0]?.initialState, false, 'toggleFace initialState should be inactive')
 equal(drawerStates[0]?.[0], 'toggleFace', 'toggleFace state should be initialized')
 equal(drawerStates[0]?.[1], false, 'toggleFace should start inactive')
 equal(events[0]?.[0], 'onFaceMode', 'initial face mode should be distributed')
 equal(events[0]?.[1], 'simple', 'initial face mode should be simple')
+
+const faces: unknown[] = []
+const callbackTarget = {
+  renderer: {
+    ...robot.renderer,
+    setFace: (face: unknown) => {
+      faces.push(face)
+    },
+  },
+}
+
+buttons[0]?.callback?.(callbackTarget)
+equal(drawerStates[1]?.[0], 'toggleFace', 'toggleFace state should update after first toggle')
+equal(drawerStates[1]?.[1], true, 'toggleFace should become active after switching to dog')
+equal(events[1]?.[1], 'dog', 'first toggle should switch to dog face mode')
+
+buttons[0]?.callback?.(callbackTarget)
+equal(drawerStates[2]?.[1], true, 'toggleFace should remain active after switching to image')
+equal(events[2]?.[1], 'image', 'second toggle should switch to image face mode')
+
+buttons[0]?.callback?.(callbackTarget)
+equal(drawerStates[3]?.[1], false, 'toggleFace should become inactive after returning to simple')
+equal(events[3]?.[1], 'simple', 'third toggle should return to simple face mode')
+equal(faces.length, 3, 'face should be replaced on each toggle')

Also applies to: 42-48

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

In `@firmware/tests/renderers/default-mod-face/main.ts` around lines 6 - 12, The
test currently registers drawer callbacks via robot.application.addDrawerButton
but never invokes them; extend the test to capture the registered button (from
the buttons array) and its callback (the addDrawerButton handler stored when
robot.application.addDrawerButton is called), extract the callback's parameters
like kind and initialState, then invoke that callback repeatedly (or with the
appropriate event payload) to assert the drawer state cycles through "simple" →
"dog" → "image" → "simple" as implemented in on-robot-created.ts; update
assertions on drawerStates and events after each invocation to verify the actual
drawer interaction logic.
🤖 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/renderers-piu/effects/speech-balloon.ts`:
- Around line 2-8: The imports for Piu constructors are currently declared with
`import type`, so runtime constructors like Container, Content, Style, Text,
Texture (and missing Skin) will be undefined; change the imports to import the
actual runtime values (not type-only) so Container, Content, Style, Text,
Texture are available at runtime and add an import for Skin as well following
the same pattern used in the other renderer files (e.g., how
drawer.ts/face-view.ts import Piu constructors), or alternatively keep type-only
imports and add separate value imports or renamed value aliases so references to
Container, Content, Style, Text, Texture, and Skin used in the file resolve to
actual constructors at runtime.

In `@firmware/tests/chats/chat-audioio-config/main.ts`:
- Around line 11-20: The test stub defines addDrawerButton on robot.application
but the code under test (onRobotCreated) uses robot.renderer.application; update
the StubRobot so addDrawerButton (and its spy) is placed under
renderer.application.application (i.e.,
robot.renderer.application.addDrawerButton) and move/attach the spy accordingly
in the tests that assert drawer-button behavior (including the similar cases at
lines 33-40) so the assertions target the actual side-effect location used by
onRobotCreated.

---

Nitpick comments:
In `@firmware/tests/renderers/default-mod-face/main.ts`:
- Around line 6-12: The test currently registers drawer callbacks via
robot.application.addDrawerButton but never invokes them; extend the test to
capture the registered button (from the buttons array) and its callback (the
addDrawerButton handler stored when robot.application.addDrawerButton is
called), extract the callback's parameters like kind and initialState, then
invoke that callback repeatedly (or with the appropriate event payload) to
assert the drawer state cycles through "simple" → "dog" → "image" → "simple" as
implemented in on-robot-created.ts; update assertions on drawerStates and events
after each invocation to verify the actual drawer interaction logic.
🪄 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: 157e6e44-e080-4542-89bd-9fa5b383f228

📥 Commits

Reviewing files that changed from the base of the PR and between 53996bb and c6675dc.

📒 Files selected for processing (13)
  • firmware/mods/chat_audioio/mod.js
  • firmware/stackchan/default-mods/on-robot-created.ts
  • firmware/stackchan/renderers-piu/behaviors/face.ts
  • firmware/stackchan/renderers-piu/effects/speech-balloon.ts
  • firmware/stackchan/renderers-piu/face-view.ts
  • firmware/tests/chats/README.md
  • firmware/tests/chats/chat-audioio-config/main.ts
  • firmware/tests/chats/chat-audioio-config/manifest.json
  • firmware/tests/chats/chat-balloon/main.ts
  • firmware/tests/renderers/default-mod-face/main.ts
  • firmware/tests/renderers/default-mod-face/manifest.json
  • firmware/tests/renderers/face-view-state/main.ts
  • firmware/tests/renderers/face-view-state/manifest.json
✅ Files skipped from review due to trivial changes (5)
  • firmware/tests/chats/README.md
  • firmware/mods/chat_audioio/mod.js
  • firmware/tests/renderers/default-mod-face/manifest.json
  • firmware/tests/renderers/face-view-state/manifest.json
  • firmware/tests/chats/chat-audioio-config/manifest.json
🚧 Files skipped from review as they are similar to previous changes (2)
  • firmware/stackchan/default-mods/on-robot-created.ts
  • firmware/stackchan/renderers-piu/behaviors/face.ts

Comment thread firmware/stackchan/renderers-piu/effects/speech-balloon.ts Outdated
Comment thread firmware/tests/chats/chat-audioio-config/main.ts
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.

🧹 Nitpick comments (2)
firmware/stackchan/renderers-piu/effects/speech-balloon.ts (1)

185-198: Minor: redundant updateText call on setText / onDisplaying.

ensureParts already invokes this.updateText(self, currentText) (Line 147) after a rebuild, and updatePalette(currentFace) (Line 146). So:

  • setText (Lines 185–189) calls updateText a second time whenever a rebuild occurred.
  • onDisplaying (Lines 195–198) calls updatePalette(currentFace) a second time after ensureParts already did.

Harmless today (palette cache short-circuits, updateText just reassigns the same string), but can be trimmed:

♻️ Proposed tweak
       setText(self: PiuContainer, text: string) {
         currentText = text ?? ''
-        this.ensureParts(self)
-        this.updateText(self, currentText)
+        // ensureParts will call updateText(currentText) if it rebuilds;
+        // otherwise we still need to push the new string through.
+        const rebuilt = !background || !bodyText || layoutWidth !== resolveWidth(self)
+        this.ensureParts(self)
+        if (!rebuilt) this.updateText(self, currentText)
       }
@@
       onDisplaying(content: PiuContainer) {
         this.ensureParts(content)
-        this.updatePalette(currentFace)
       }

(Requires hoisting resolveWidth/layoutWidth/background/bodyText visibility, which they already have in the closure.)

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

In `@firmware/stackchan/renderers-piu/effects/speech-balloon.ts` around lines 185
- 198, The setText and onDisplaying methods redundantly call updateText and
updatePalette after ensureParts already performs those updates; remove the
duplicate calls in setText (remove this.updateText(self, currentText)) and in
onDisplaying (remove this.updatePalette(currentFace)) so ensureParts is the
single place that updates text and palette during a rebuild, or alternatively
wrap those calls with a cheap guard that skips if ensureParts already ran (use
currentText/currentFace state checks); update references: setText, onDisplaying,
ensureParts, updateText, updatePalette, currentText, currentFace.
firmware/stackchan/renderers-piu/effects/emoticon.ts (1)

9-14: Optional: intersecting PiuContent with PiuShape for WithSkin.

WithSkin now intersects PiuContent with Omit<PiuShape, 'fillOutline' | 'strokeOutline'>. Since all call sites construct shapes (not bare contents) via defineShapeTemplate, typing this directly as Omit<PiuShape, ...> & { skin?: PiuSkin; fillOutline?: unknown; strokeOutline?: unknown } would be more accurate and drop the need to intersect with PiuContent. Not blocking — the current intersection typechecks.

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

In `@firmware/stackchan/renderers-piu/effects/emoticon.ts` around lines 9 - 14,
The current WithSkin type unnecessarily intersects PiuContent with
Omit<PiuShape, 'fillOutline' | 'strokeOutline'>; update WithSkin to be based
directly on the shape type so it reflects actual call sites: replace the
intersection with a single type defined as Omit<PiuShape, 'fillOutline' |
'strokeOutline'> & { skin?: PiuSkin; fillOutline?: unknown; strokeOutline?:
unknown } (remove PiuContent from the union); ensure usages in
defineShapeTemplate still type-check after this change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@firmware/stackchan/renderers-piu/effects/emoticon.ts`:
- Around line 9-14: The current WithSkin type unnecessarily intersects
PiuContent with Omit<PiuShape, 'fillOutline' | 'strokeOutline'>; update WithSkin
to be based directly on the shape type so it reflects actual call sites: replace
the intersection with a single type defined as Omit<PiuShape, 'fillOutline' |
'strokeOutline'> & { skin?: PiuSkin; fillOutline?: unknown; strokeOutline?:
unknown } (remove PiuContent from the union); ensure usages in
defineShapeTemplate still type-check after this change.

In `@firmware/stackchan/renderers-piu/effects/speech-balloon.ts`:
- Around line 185-198: The setText and onDisplaying methods redundantly call
updateText and updatePalette after ensureParts already performs those updates;
remove the duplicate calls in setText (remove this.updateText(self,
currentText)) and in onDisplaying (remove this.updatePalette(currentFace)) so
ensureParts is the single place that updates text and palette during a rebuild,
or alternatively wrap those calls with a cheap guard that skips if ensureParts
already ran (use currentText/currentFace state checks); update references:
setText, onDisplaying, ensureParts, updateText, updatePalette, currentText,
currentFace.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 69724d7a-bca9-4fd9-bc6a-21e448004de9

📥 Commits

Reviewing files that changed from the base of the PR and between c6675dc and cd5b6cb.

📒 Files selected for processing (11)
  • firmware/stackchan/renderers-piu/behaviors/face.ts
  • firmware/stackchan/renderers-piu/common-view.ts
  • firmware/stackchan/renderers-piu/effects/emoticon.ts
  • firmware/stackchan/renderers-piu/effects/multirow-balloon.ts
  • firmware/stackchan/renderers-piu/effects/speech-balloon.ts
  • firmware/stackchan/renderers-piu/face-view.ts
  • firmware/stackchan/renderers-piu/parts/dog/eyebrow.ts
  • firmware/stackchan/renderers-piu/parts/dog/mouth.ts
  • firmware/stackchan/renderers-piu/parts/dog/nose.ts
  • firmware/stackchan/renderers-piu/parts/eye.ts
  • firmware/stackchan/renderers-piu/template.ts
✅ Files skipped from review due to trivial changes (1)
  • firmware/stackchan/renderers-piu/effects/multirow-balloon.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • firmware/stackchan/renderers-piu/common-view.ts
  • firmware/stackchan/renderers-piu/face-view.ts
  • firmware/stackchan/renderers-piu/behaviors/face.ts

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: 1

🤖 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/tests/chats/chat-service/main.ts`:
- Around line 50-53: The test is silently falling back to numeric 3 when
ChatAudioIOAny.CONNECTED is missing; instead remove the nullish fallback and
fail fast: before calling instance.emitState, assert that
ChatAudioIOAny.CONNECTED is defined (throw or use expect) and then call
instance.emitState(ChatAudioIOAny.CONNECTED); reference
ChatAudioIOAny.CONNECTED, instance.emitState, and the ChatAudioIO instance to
locate and update the test so a missing mock constant causes the test to fail
rather than emit a hidden magic value.
🪄 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: f3b6775e-3c68-4145-a1f3-ed20f607e9f7

📥 Commits

Reviewing files that changed from the base of the PR and between cd5b6cb and 796a3ef.

📒 Files selected for processing (2)
  • firmware/tests/chats/chat-service/main.ts
  • firmware/tests/renderers/default-mod-face/main.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • firmware/tests/renderers/default-mod-face/main.ts

Comment thread firmware/tests/chats/chat-service/main.ts Outdated
@meganetaaan meganetaaan merged commit 031a6db into dev/v1.0 Apr 23, 2026
5 checks passed
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