Files
moeka-project/packages/stage-ui
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
..
2026-03-12 05:09:32 +08:00
2026-08-26 20:13:10 +08:00
2026-08-26 20:13:10 +08:00
2026-08-26 20:13:10 +08:00

Stage UI

Shared core for stage

Character-card module settings

The card store owns three distinct states:

  • moduleDefaults stores global provider, model, voice, and display selections.
  • Each card stores explicit overrides. An empty string means inherit.
  • Module stores expose the resolved runtime selections used by the application.

Use configureForAuthentication for login and logout. It updates global defaults, then reapplies the active card without saving defaults into that card. Use card commands for activation and explicit edits. Settings pages must not save cards from watchers: authentication and remote snapshots also trigger them. The synchronization leader owns these commands; followers receive snapshots.

Models inherit only within the same provider. Voices also require the same model. A different provider without a model stays unconfigured rather than receiving an unrelated model id. The editor requires a model for an explicit chat or vision provider unless that model can be inherited safely.

Defaults are seeded once from the current runtime on upgrade. This cannot recover historical global values that an older card already overwrote. Existing speech-noop selections are preserved because they may represent intentional silence. Users can explicitly choose Inherit global settings in the editor; importing or saving an unrelated card field does not change it.

Button analytics

Register the shared plugin once in each Vue application:

import { trackButtonPlugin } from '@proj-airi/stage-ui/directives/track-button'

createApp(App)
  .use(trackButtonPlugin)
  .mount('#app')

Buttons that represent a product-analysis click intent can then declare a typed event without wrapping their business handler:

<Button
  v-track-button="{ name: 'update_check_clicked', channel: selectedChannel }"
  @click="checkForUpdates()"
/>

Keep async outcomes, confirmed state changes, impressions, and lifecycle events in their owning business flows instead of attaching them to the initial click.

Histoire (UI storyboard)

https://histoire.dev/

pnpm -F @proj-airi/stage-ui run story:dev

Project structure

  1. If a story is bound to a specific component, it can be placed beside the component in the src folder. e.g., MyComponent.story.vue
  2. If a story is not bound to a specific component, then it should be placed in the stories folder. e.g., MyStory.story.vue