16 Commits
Author SHA1 Message Date
leafyy 3c9e60907c test(stage-tamagotchi): stabilize interactive area browser fixtures (#2510)
## Summary

The desktop browser suite can mount `InteractiveArea` before its utility
styles settle, then scroll history while the reply composer is still
expanding. A pending Virtua tail scroll can replace the message that the
test selected and make the scrollport assertion fail.

This addresses the pre-existing browser-test failure reported during
#2506 ([CI
failure](https://github.com/moeru-ai/airi/actions/runs/34448585692/job/102804067154)).

- Scan desktop and shared Vue components before the initial test
stylesheet is served. Load the desktop UnoCSS config explicitly so
config discovery does not replace the scan.
- Give each fixture a window-sized host, reset the viewport, and dispose
the component, host, and Pinia after each test.
- Wait for the reply transition and virtual-list measurements to settle
before scrolling. Cover both 200 ms and 1 s transitions, and assert the
requested scroll position throughout the existing message-mount checks.

Only browser tests and their configuration change.

## Validation

The complete desktop browser suite passed with three shuffled seeds: **6
files, 65 tests per run**, with no skipped tests.

```sh
for seed in 2508 2506 2507; do
  npm_config_registry=https://registry.npmjs.org/ NODE_OPTIONS=--no-experimental-webstorage \
    pnpm exec vitest run --config apps/stage-tamagotchi/vitest.config.ts \
    --project browser --sequence.shuffle --sequence.seed="$seed" --bail=1
done
npm_config_registry=https://registry.npmjs.org/ pnpm run typecheck
npm_config_registry=https://registry.npmjs.org/ pnpm run lint
git diff --check
```

Typecheck passed all 52 tasks. Lint passed with existing repository
warnings. ResizeObserver loop warnings remain visible; this change does
not suppress them. Linux CI validation is pending.
2026-09-10 17:41:29 +08:00
leafyy d7f38783f7 fix(stage-ui): persist speech provider settings (#2497) 2026-09-10 00:21:06 +08:00
leafyy 7bcd8e5274 test(stage-ui): fix browser fixtures and cleanup (#2492)
## Description

The Unit Test job reaches stage-ui, then fails and stalls in browser
tests. The same failures occur on the parent commit.

- Use the real consciousness store and Pinia refs in context bridge
tests. Check that input requests preserve temperature and top-p.
- Dispose each bridge and Pinia scope after every test, including failed
assertions. This prevents old listeners from receiving later stream
events.
- Create translated module stores during component setup and await card
initialization before drawer interactions. Dispose the fixture stores
after each test.
- Remove the Pinia and Mutex replacements from the context bridge
fixture.

This change only updates two test files.

## Verification

The original fixture reproduced three failures in the two affected
files. The updated files pass all 17 tests. The complete CI test command
passes 2,534 tests, with one skipped test. Stage-ui passes all 822
tests.

- `NODE_OPTIONS=--no-experimental-webstorage pnpm exec vitest run
--config packages/stage-ui/vitest.config.ts --project browser
packages/stage-ui/src/components/misc/character-switcher-drawer.browser.test.ts
packages/stage-ui/src/stores/mods/api/context-bridge.contract.browser.test.ts
--reporter=verbose`
- `NODE_OPTIONS=--no-experimental-webstorage pnpm run test:run`
- `pnpm run typecheck`
- `pnpm run lint`
- `sem diff --staged --no-cosmetics -v --file-exts .ts .tsx`, JSON diff,
and dependent analysis for `mountSwitcher`: no blocking findings.
- `git diff --cached --check`

GitHub CI also passes on this PR: [all nine CI jobs
succeeded](https://github.com/moeru-ai/airi/actions/runs/34306753310),
including [Unit
Test](https://github.com/moeru-ai/airi/actions/runs/34306753310/job/102324934330).

## Additional Context

[Reported CI
failure](https://github.com/moeru-ai/airi/actions/runs/34237304157/job/102098223378)
· [Parent commit
failure](https://github.com/moeru-ai/airi/actions/runs/34212756766/job/102017352566)
2026-09-09 11:55:32 +08:00
leafyy 52a9f429ed feat(plugin-host): add combined lifecycle controls (#2476)
## Summary

- Add combined `Enable and Load` and `Disable and Unload` controls to
Plugin Host Debug.
- Keep enablement persistence and runtime lifecycle changes in one
sequential store action.
- Add tests for ordering, busy state, and failure handling.
- Localize the new controls and fallback error messages in English and
Simplified Chinese.

## Verification

- `pnpm exec vitest run --config packages/stage-ui/vitest.config.ts
--project node src/stores/devtools/plugin-host-debug.test.ts`
- `pnpm -F @proj-airi/i18n test`
- `pnpm -F @proj-airi/i18n build`
- `pnpm run build:packages`
- `pnpm typecheck`
- `pnpm lint`
- Manually tested the bundled `devtools-sample-plugin` in the Electron
Plugin Host Debug page. Both combined actions updated the persisted
registry and runtime session state without restarting the app.

## Visual changes

| Before | After |
|---|---|
| ![Plugin Host Debug
before](https://github.com/user-attachments/assets/c9b3a6ec-699b-444a-ba01-50bbc2ffa3ad)
| ![Plugin Host Debug
after](https://github.com/user-attachments/assets/889dec79-a157-42a8-98f5-906d2e0b41f3)
|
| Plugin Host Debug | Plugin Host Debug with combined lifecycle controls
|
2026-09-07 19:04:17 +08:00
leafyy 05ac66edfa fix(stage-tamagotchi): show Live2D expressions in settings (#2451)
## Summary

- Send a serializable Live2D expression snapshot from the stage renderer
to the separate Settings window.
- Route expression changes back to the renderer that owns the loaded
model, with owner ID checks for stale-window isolation.
- Add browser regressions for the component boundary and the complete
BroadcastChannel round trip.

Fixes #2450

## Verification

- `pnpm typecheck`
- `pnpm lint`
- `../../node_modules/.bin/vitest run --project browser
src/components/scenarios/settings/model-settings/live2d.browser.test.ts`
- `node_modules/.bin/vitest run --config
apps/stage-tamagotchi/vitest.config.ts --project browser
apps/stage-tamagotchi/src/renderer/composables/model-settings-runtime.browser.test.ts`
- `./node_modules/.bin/electron-vite build`
- Vishot Electron capture with the issue Live2D fixture on the merge
base and this branch
- `sem diff upstream/main...HEAD --no-cosmetics -v --file-exts .ts .tsx`

## Visual changes

| Before | After |
|---|---|
|
![](https://github.com/user-attachments/assets/e383ebf3-d60e-4f66-8550-4cab0dc61261)
|
![](https://github.com/user-attachments/assets/b8aee712-c227-44d0-a60c-2dd24cc0b0cd)
|
| Settings / Models / Live2D expressions: empty list | Settings / Models
/ Live2D expressions: model entries are available |
2026-09-07 11:14:44 +08:00
leafyy 2ca8339a49 fix(speech): synchronize streaming catalog state (#2445) 2026-09-03 19:20:35 +08:00
leafyy 13bbad95dc fix(stage-pages): initialize streaming speech provider config (#2440) 2026-09-02 19:00:05 +08:00
leafyy 55ef3d416e fix(stage-ui-three): preserve imported VRM view settings (#2423) 2026-09-01 10:31:12 +08:00
leafyy aa69ee62b5 refactor(lipsync): share renderer vowel policy (#2374)
## Description

The VRM and MMD renderers had the same two boundary problems:

- Both imported the Stage audio store from the `stage-ui` source tree.
- Both kept copies of the wLipSync profile and model-neutral vowel
policy.

This PR solves both problems as one architecture change.

```text
Stage owns AudioContext and the current audio source
  -> ThreeScene / MMDScene receive both as inputs
    -> model-driver-lipsync owns the profile and shared vowel policy
      -> VRM maps AEIOU to aa / ee / ih / oh / ou
      -> MMD maps AEIOU to vowelA / vowelE / vowelI / vowelO / vowelU
```

The shared driver owns viseme projection, winner and runner selection,
silence handling, and smoothing. It has no Vue, Three.js, VRM, or MMD
dependency. Each renderer keeps only its model-specific mapping.

The renderer packages no longer import the Stage audio store or depend
directly on `wlipsync`. Stage passes the owning `AudioContext` down with
the current audio source. Cleanup disconnects only the wLipSync node
owned by that renderer.

## Current solution

The package-boundary review identified two related findings:

- Finding 3: The VRM and MMD renderers imported `stage-ui` audio state
through source paths.
- Finding 4: The same renderers duplicated the wLipSync profile and
model-neutral vowel policy.

Both findings came from one missing boundary between Stage, the lip-sync
driver, and the renderer adapters. This PR fixes that boundary with one
ownership change.

| Layer | Responsibility |
| --- | --- |
| `stage-ui` | Owns the `AudioContext` and the current audio source. It
passes both values to each renderer. |
| `stage-ui-three` and `stage-ui-mmd` | Connect renderer components to
the shared driver. They do not import the Stage audio store. |
| `model-driver-lipsync/runtime/wlipsync` | Exposes `createWLipSyncNode`
as the public runtime entry. |
| `model-driver-lipsync/shared/wlipsync` | Owns the profile, public
types, and model-neutral vowel policy. |

Runtime flow:

1. `Stage.vue` passes `audioContext` and `currentAudioSource` to
`ThreeScene` or `MMDScene`.
2. The renderer composable watches the context and creates one wLipSync
node for that context.
3. The source watcher connects the current audio source to that node.
4. Each render frame sends the wLipSync values to
`createWLipSyncVowelDriver`.
5. The renderer adapter applies the returned AEIOU weights to its model
controls.
6. Cleanup disconnects the exact source and node pair that the renderer
owns.

The shared policy keeps the current renderer behavior:

- It maps the raw `S` viseme to `I`.
- It selects the two strongest vowels.
- It uses the existing volume and silence thresholds.
- It keeps the 160 ms silence window.
- It keeps the existing attack, release, cap, and output scale values.

The renderer-specific mappings stay in their renderer packages:

- VRM maps AEIOU to `aa`, `ee`, `ih`, `oh`, and `ou`.
- MMD maps AEIOU to `vowelA`, `vowelE`, `vowelI`, `vowelO`, and
`vowelU`.

This solution removes both duplicate profile files. It also removes the
direct `wlipsync` dependencies from both renderer packages.

## Linked Issues

None.

## Additional Context

There is no intended visual change. This refactor preserves the existing
vowel weights and renderer mappings.

Verification:

- `sem diff --staged --no-cosmetics -v --file-exts .ts .tsx`
- `sem diff --staged --no-cosmetics -v --file-exts .vue`
- `sem impact --entity-id <useVRMLipSync> --json --dependents`
- `sem impact --entity-id <useMMDLipSync> --json --dependents`
- `pnpm -F @proj-airi/model-driver-lipsync test` — 3 tests passed
- `pnpm -F @proj-airi/stage-ui-three exec vitest run` — 18 tests passed
- `pnpm -F @proj-airi/stage-ui-mmd exec vitest run` — 23 tests passed
- Type checks passed for `model-driver-lipsync`, `stage-ui-three`,
`stage-ui-mmd`, and `stage-ui`
- `pnpm install --frozen-lockfile --ignore-scripts`
- `pnpm lint` — 0 errors and 7 existing warnings outside this change
- `git diff --check`

The full workspace `pnpm typecheck` was also run. In the isolated
worktree it stopped in unchanged `electron-vueuse` and
`plugin-sdk-tamagotchi` because their generated dependency outputs were
not present. All affected package type checks above passed.
2026-08-31 14:58:43 +08:00
leafyy 4f38487447 fix(stage-pocket): add iOS microphone usage description (#2389) 2026-08-28 17:44:30 +08:00
leafyy 795c43f0a7 refactor(stage-pages): share polaroid page (#2375) 2026-08-27 17:10:34 +08:00
leafyy debe4019d1 refactor(stage-ui): use public package exports (#2373) 2026-08-27 17:08:44 +08:00
leafyy 7756d921a7 fix(stage-ui): define deterministic chat math syntax (#2328) 2026-08-20 16:30:01 +08:00
leafyy cf8811d5eb feat(stage-ui): add edit mode for airi card (#939) 2026-01-12 18:53:14 +08:00
leafyy 164ecf5220 feat(stage-tamagotchi): disable back button on settings home page (#931) 2026-01-11 03:36:24 +08:00
leafyy 8736db16b3 fix(stage-pages): fix the cancel text display problem in settings -> data (#924) 2026-01-09 19:24:52 +08:00