* feat: add draggable sidebar resizer
- Add resizer handle between map and event panel
- Sidebar width adjustable from 300px to 50% screen width
- Default width remains 400px
- Auto-adjust on window resize
* fix: preserve map zoom when resizing sidebar
Only update viewport screen size without re-fitting the map
* fix: use window size for canvas to prevent map scaling on resize
- Canvas size now based on window instead of container
- Sidebar resize only clips the visible area, map stays same size
- Remove CSS stretch on canvas element
* docs: update sidebar-resizer spec with canvas fix
* fix: use str() instead of .value for realm i18n display
Fix bug where user-facing messages displayed raw enum values like
"FOUNDATION_ESTABLISHMENT" instead of translated names like "筑基".
The Realm and Stage classes already have __str__ methods that return
i18n translated text, but several places were incorrectly using
.value which returns the raw enum string.
Changed files:
- src/classes/single_choice.py: item exchange messages
- src/classes/kill_and_grab.py: loot messages
- src/classes/fortune.py: fortune discovery messages
- src/classes/avatar/inventory_mixin.py: purchase error messages
Also added unit tests and integration tests to prevent regression.
* test: add integration tests for all modified files
Add tests covering:
- kill_and_grab.py: context string realm display
- fortune.py: weapon/auxiliary intro realm display
- inventory_mixin.py: can_buy_item error message realm display
* test(web): add comprehensive tests for all Pinia stores
* test(web): add edge case tests for Pinia stores
* test(web): document race condition bugs in ui and world stores
* fix(web): fix race condition bugs in ui and world stores
- ui.ts: Add detailRequestId counter to prevent stale responses from
overwriting fresh data when reselecting the same target
- world.ts: Add eventsRequestId counter to prevent stale responses
when filter changes rapidly via resetEvents
- Update tests to verify the fix works correctly
* fix(web): fix race condition in fetchInitStatus
- Add fetchStatusRequestId counter to prevent stale responses from
overwriting fresh data when fetchInitStatus is called rapidly
- Add test that first proves the bug exists, then verifies the fix
* fix(web): fix race condition in fetchState
Add fetchStateRequestId counter to prevent stale responses from
overwriting fresh data when fetchState is called rapidly.
* test(web): add missing edge case tests for world store
- Add changePhenomenon API failure test
- Add initialize concurrent calls test
- Add getPhenomenaList concurrent calls test
Total: 108 tests
* test(web): add comprehensive socket store tests
- Add init() duplicate call guard test
- Add setup listener tests
- Add message handling tests (tick, game_reinitialized)
- Add status change handling tests
Total: 118 tests
* test(web): add missing socket message handling tests
- Add llm_config_required message tests
- Add unknown message type test
Total: 121 tests
* test(web): add handleTick edge case tests
- Add test for avatars without id (ignored)
- Add test for empty events array
- Add test for events filtered to empty
Total: 124 tests
* test: add edge case tests for game initialization
* test: add 3 more edge case tests for game initialization
* test: add 8 more edge case tests for comprehensive coverage
* docs: add comprehensive coverage report to test file header
Use single-pass regex replacement instead of multiple replaceAll calls.
This prevents shorter names from matching inside already-replaced longer names.
For example, with names '张三' and '张三丰', the text '张三丰是大师' now
correctly highlights only '张三丰', not '张三' within it.
* fix: make cooldown_action decorator properly await async finish()
The decorator was incorrectly wrapping the async finish() method with a sync
wrapper, causing cooldown to be recorded BEFORE the action actually executed.
This fix:
- Changes wrapper to async def finish()
- Awaits original_finish() before recording cooldown
- Ensures cooldown is only recorded on successful execution
Fixes#74
* test: add test to verify cooldown not recorded on failure
This test actually reveals the async/await bug:
- Creates a FailingAction that raises in finish()
- Verifies cooldown is NOT recorded when action fails
- Fails with buggy sync wrapper, passes with async fix
- Test LLM response parsing (list and dict formats)
- Test null params conversion to empty dict
- Test invalid format handling and skipping
- Test emotion update logic with all emotion types
- Test fallback to CALM on invalid/missing emotion
- Test batch avatar processing
- Test AI.decide wrapper returns NULL_EVENT
- Test thinking field variants (avatar_thinking vs thinking)
- Add testing strategy documentation to test file
Closes#63
PR #32 changed Technique.sect (str) to Technique.sect_id (int), but
fortune.py was not updated. This caused AttributeError when fortune
event triggered and tried to access t.sect.
Error: 'Technique' object has no attribute 'sect'
Ensures all tests have deterministic random behavior by setting
random.seed(42) before each test. This prevents flaky tests caused
by random number generation.
Closes#44
When avatars overlap (e.g., during sparring, talking, dual cultivation),
it's hard to click on them directly. This adds the ability to click on
colored avatar names in the event panel to open their detail view.
- Modify highlightAvatarNames to include data-avatar-id attribute
- Add click event delegation in EventPanel
- Add hover styles for clickable names