Skip to content

fix(dashboard): count down to a lock that has not expired - #3910

Open
ntdatt812 wants to merge 1 commit into
decolua:masterfrom
ntdatt812:fix/cooldown-timer-expired-lock
Open

ntdatt812 wants to merge 1 commit into
decolua:masterfrom
ntdatt812:fix/cooldown-timer-expired-lock

Conversation

@ntdatt812

Copy link
Copy Markdown
Contributor

A connection carries one flat field per model lock — modelLock_<model>, plus modelLock___all for the account-level one. Both provider cards picked the timestamp for their countdown like this:

const modelLockUntil = Object.entries(connection)
  .filter(([k]) => k.startsWith("modelLock_"))
  .map(([, v]) => v).filter(Boolean).sort()[0] || null;

Earliest lock, expired or not. isCooldown is computed separately, a few lines below, by a scan that does compare against Date.now().

So on a connection with one lapsed lock and one still live — which is the normal state after a per-model cooldown ends while an account-level one is running, or the other way round — the two disagree:

  • isCooldown → true, from the live lock. Correct.
  • modelLockUntil → the lapsed one, because it sorts first.

CooldownTimer sets its label to "" and returns null as soon as its target is in the past. The badge says the connection is in cooldown and the countdown beside it is simply gone, with no error and nothing in the console. ConnectionRow.js even carries a comment saying the effect handles the Date.now() comparison — it does, but only for isCooldown, not for the value that reaches the timer.

The change

getEarliestModelLockUntil already exists in open-sse/services/accountFallback.js, its docblock says "Used for UI cooldown display", and it skips expired entries. Both cards now call it — for the timer target and for isCooldown — instead of keeping a fourth and fifth hand-rolled copy of the scan.

The literal "modelLock_" is currently spelled out in six places across the dashboard and once more as a local const in src/app/api/models/availability/route.js, all of them re-deriving what MODEL_LOCK_PREFIX and this helper already say. I have limited this PR to the two that were actually wrong; the rest is a tidy-up that can stand on its own.

Verification

npx vitest@3 run --config tests/vitest.config.js tests/unit/cooldown-timer-expired-lock.test.js
→ 9 passed

npx next build — 0 errors.

The first five pin the helper's behaviour, including the exact reported shape: a lapsed lock that sorts first alongside two live ones.

The last four are a source-level check that both cards import the helper and no longer scan modelLock_ by hand. They are there because this bug is invisible to a behavioural test — the cards are client components and this repo has no React renderer in its test setup, so a card could go back to its own inline scan with every other test still green. That is exactly how the two copies drifted apart in the first place. Reverting ConnectionsCard.js alone fails those two cases and nothing else, which is the check I ran.

Both provider cards picked the CooldownTimer target with

  .filter(([k]) => k.startsWith("modelLock_")).map(([, v]) => v)
    .filter(Boolean).sort()[0]

- the earliest lock, expired or not. isCooldown came from a separate scan
that did compare against now, so a connection carrying one lapsed lock and
one live one showed the cooldown badge (correct) beside a timestamp in the
past. CooldownTimer renders nothing once its target has gone by, so the
countdown silently disappeared exactly when there was more than one lock.

getEarliestModelLockUntil already existed for this -- its docblock says
"Used for UI cooldown display" -- and skips expired entries. Both cards now
ask it instead of keeping a fourth and fifth copy of the scan.
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