Skip to content

fix: coalesce overlapping usage refreshes and harden companion-state decoding - #123

Merged
chattymin merged 1 commit into
mainfrom
fix/refresh-coalescing-and-state-decode-resilience
Jul 26, 2026
Merged

chattymin merged 1 commit into
mainfrom
fix/refresh-coalescing-and-state-decode-resilience

Conversation

@chattymin

Copy link
Copy Markdown
Owner

Summary

Two resilience fixes from a code audit.

Refresh coalescing

refresh() dropped any call that overlapped an in-flight refresh
(guard !isRefreshing). Re-enabling keychain access while a poll was
running dropped the reload; in "manual" (interval 0) mode there is no
follow-up poll, so Claude limits stayed blank until the next manual action.
Overlapping calls now set a refreshPending flag and the in-flight refresh
re-runs once on completion.

Companion-state decode resilience

One corrupt or legacy field in companion-state.json could throw the whole
decode, silently resetting the entire companion, Pokedex, and inventory.

  • The Pokedex decodes per-entry (one broken entry is dropped, the rest kept).
  • Every field absorbs missing/null/type-mismatch into its default — a
    corrupt active falls back to an egg while the dex and inventory survive.
  • Only a non-object top-level still fails; load() then backs the original
    up to .corrupt before starting fresh, leaving room for manual recovery.

Testing

  • New tests: refresh coalescing (actor-gated overlap), per-entry dex
    isolation, corrupt-active fallback, corrupt-file backup.
  • ./scripts/test-gate.sh — 322 tests pass, 85% core line coverage.

🤖 Generated with Claude Code

…decoding

Two resilience fixes surfaced by a code audit.

Refresh coalescing (UsageStore). refresh() dropped any call that overlapped
an in-flight refresh (`guard !isRefreshing`). Re-enabling keychain access
(disableKeychainAccess) while a poll was running dropped the reload; in
"manual" (interval 0) mode there is no follow-up poll, so Claude limits
stayed blank until the next manual action. Overlapping calls now set a
`refreshPending` flag and the in-flight refresh re-runs once on completion,
bounded by the request arrival rate.

Companion-state decode resilience (CompanionModel / CompanionStore). One
corrupt or legacy field in companion-state.json could throw the whole
CompanionState decode, silently resetting the entire companion, Pokedex,
and inventory. Now:
- the Pokedex decodes per-entry (a broken entry is dropped, the rest kept);
- every field absorbs missing/null/type-mismatch into its default, so one
  bad field no longer resets everything (a corrupt active falls back to an
  egg while the dex and inventory survive);
- only a non-object top-level still fails to decode, and load() then backs
  the original file up to `.corrupt` before starting fresh, leaving room
  for manual recovery.

Tests: refresh coalescing (actor-gated overlap), per-entry dex isolation,
corrupt-active fallback, and corrupt-file backup. Full gate: 322 tests pass,
85% core line coverage.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@chattymin
chattymin merged commit 6ec63ba into main Jul 26, 2026
2 checks passed
@chattymin
chattymin deleted the fix/refresh-coalescing-and-state-decode-resilience branch July 26, 2026 07:54
@heli-os heli-os mentioned this pull request Sep 25, 2026
5 of 10 tasks
chattymin pushed a commit that referenced this pull request Sep 25, 2026
Korean copy wrote both particle forms after runtime Pokémon names
("유니란이(가)", "듀란(으)로") because the name is not known when the
string is translated. Pick the particle from the final syllable's coda
instead, with ㄹ taking "로".

- Add KoreanParticle (subject 이/가, object 을/를, direction 으로/로).
  Names that do not end in a Hangul syllable, such as Unown's "[A]" or a
  "#123" placeholder, keep the combined form.
- Use it in the six Korean strings that wrote both forms. Other
  languages are unchanged.
- Add the helper to the coverage gate's logic core.

No new dependency: es-hangul is JavaScript, and the check is a few lines
of Unicode arithmetic.

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Strongorange added a commit to Strongorange/PokeTokenBar-windows that referenced this pull request Oct 20, 2026
Port macOS chattymin#356 (KoreanParticle.swift): hatch, shiny hatch, evolve,
level-up status, egg confirm and mint feedback now append the particle
matching the name's batchim (을/를, 이/가, 으로/로, with ㄹ-coda taking
로) instead of the parenthesized dual forms. Non-Hangul endings (Unown
markers, pre-load 'chattymin#123', Latin names) keep the dual form — a wrong
particle is worse than a parenthetical.

Also fixes the mint feedback passing the raw enum name ('Jolly') instead
of the localized nature, which would have kept '(으)로' visible in
Korean copy.
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