You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
S8: UpdateDelayer signature is now (retryCount, abortSignal); ComputedState
._updateCycle tracks a consecutive-error counter (reset on success) and the
default delayers apply RetryDelaySeq (1 s -> 1 min) backoff for retryCount > 0
plus a 32 ms minDelay floor -- C# UpdateDelayer/FixedDelayer parity.
FixedDelayer.get / UIUpdateDelayer.get floor the requested delay at minDelay
like C# FixedDelayer.Get -- get(0) no longer silently returns the zero delayer
(a truly zero delayer requires the explicit FixedDelayer.zero escape hatch).
S9: UIActionTracker tracks lastResultAt and exposes areInstantUpdatesEnabled()
(active OR within a 300 ms instant-update window after the last completion);
run/call decrement + timestamp + trigger `changed` immediately, dropping the
artificial 50 ms sleep. UIUpdateDelayer consults the new predicate.
S10: UIUpdateDelayer enforces minDelay (~32 ms), measured from delay start,
on every short-circuit path including the S9 instant-updates path -- mirrors
C# UpdateDelayer.Delay (race the instant predicate against the delay, never
return before minDelay).
S17: useMutableState returns { value, error, set, state } (valueOrUndefined +
error) so an error result renders instead of throwing.
S18: UIActionTracker.errors dedups same-name/same-message errors seen within
~1 s (C# MaxDuplicateRecency) with a size cap as a backstop.
Tests: new ts/packages/fusion/tests/stateui.test.ts covers S8 backoff growth
+ reset, the get(0) minDelay floor, S9 in/out-of-window behavior, S10 minDelay
floor, S17 error shape, S18 dedup+cap. delayer-cleanup.test.ts updated to the
(retryCount, abortSignal) delayer signature. Full suite: 695/695 green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SDiBQM5rnhyBG1asuP1kDi
Copy file name to clipboardExpand all lines: docs/plans/ts-port-audit.md
+10Lines changed: 10 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -336,6 +336,8 @@ Confidence: confirmed.
336
336
337
337
### S8. No retry backoff: `UpdateDelayer` has no `retryCount`, no `RetryDelays`, no transient-error tracking
338
338
339
+
Status: **closed** — fixed 2026-07-15 (batch stateui; delayer signature `(retryCount, abortSignal?)`, `RetryDelaySeq.exp(1 s, 60 s)` backoff, 32 ms min-delay floor incl. `FixedDelayer.get(0)`; all errors treated as transient — TS has no transiency classification).
340
+
339
341
Confidence: confirmed.
340
342
341
343
- TS: `UpdateDelayer` is `(abortSignal?) => Promise<void>` (`update-delayer.ts:2`) — the delay cannot depend on failure history. No `StateSnapshot`, so no `RetryCount`/`ErrorCount`. Error recovery relies solely on the global 1 s auto-invalidation (K5) — a constant ~1 s retry loop forever.
@@ -346,6 +348,8 @@ Confidence: confirmed.
346
348
347
349
### S9. `UIActionTracker` has no post-action instant-update window; the 50 ms buffer is ineffective and adds latency
348
350
351
+
Status: **closed** — fixed 2026-07-15 (batch stateui; 300 ms `instantUpdatePeriod`, the 50 ms sleep removed).
352
+
349
353
Confidence: confirmed.
350
354
351
355
- TS: `ui-action-tracker.ts:12-14` — `isActive` is true only while `_activeCount > 0`. In `run`/`call` (`ui-action-tracker.ts:27-31, 45-49`) the `finally` decrements first, then waits 50 ms, then triggers `changed`: (a) during the 50 ms "buffer", `isActive` is already false, so a `UIUpdateDelayer` consulted when the post-command invalidation arrives waits the full delay — the buffer buys nothing; (b) `await uiActions.call(fn)` returns 50 ms late.
@@ -356,6 +360,8 @@ Confidence: confirmed.
356
360
357
361
### S10. `UIUpdateDelayer` skips the delay entirely (no `MinDelay` floor) while a UI action is active — hot-loop risk
358
362
363
+
Status: **closed** — fixed 2026-07-15 (batch stateui; minDelay measured from delay start on every path).
364
+
359
365
Confidence: confirmed.
360
366
361
367
- TS: `ui-update-delayer.ts:15` — `if (t.isActive) return Promise.resolve();` and the `onChanged` cancel path also resolves with zero residual delay.
@@ -430,13 +436,17 @@ Confidence: confirmed. TS leaves `_computed` invalidated until the next `update(
430
436
431
437
### S17. `useMutableState` render throws if the state holds an error result
Confidence: confirmed. `use-mutable-state.ts:41` returns `state.value`, whose getter throws on error output — and the setter accepts `Result<T>`, so `setter(errorResult(e))` is a supported call. Next render of every component using the state throws, unmounting the tree absent an error boundary.
434
442
435
443
-**Recommended:** return `{ value, error, set }` (the shape `useComputedState` already uses) so error results render instead of throwing.
436
444
-**Alternative:** narrow the setter to plain values (no `Result`) and document that errors can't be stored via this hook. Smaller, but loses parity with `MutableState.set`'s contract.
437
445
438
446
### S18. `UIActionTracker.errors` grows unbounded with no dedup
Confidence: confirmed. Every failure is pushed to a plain array; only manual `dismissError` removes entries (`ui-action-tracker.ts:10, 26, 43`). C# dedups same-type/same-message failures within `MaxDuplicateRecency` (1 s) (`UIActionFailureTracker.cs:64-98`). Combined with S8's fixed 1 s retry, an error-toast UI floods and memory grows for the session's lifetime.
441
451
442
452
-**Recommended:** port the recency-based dedup (same error name + message within ~1 s is dropped) plus a size cap as a backstop.
0 commit comments