Skip to content

Cost feature fixes: /stats scope, burst breakdown, table overflow, strip persistence, admin landing - #51

Merged
aakri0 merged 1 commit into
mainfrom
fix/cost-deep-fixes
May 2, 2026
Merged

Cost feature fixes: /stats scope, burst breakdown, table overflow, strip persistence, admin landing#51
aakri0 merged 1 commit into
mainfrom
fix/cost-deep-fixes

Conversation

@aakri0

@aakri0 aakri0 commented May 2, 2026

Copy link
Copy Markdown
Owner

Six bugs in one pass — most are cost-related, plus the Admin tab UX you asked for.

(1) /stats leaked other users' jobs to admins

principal.can_view() returns True for admins regardless of owner, so the recent-counts on the dashboard included every user's job. /jobs and /usage/cost are already strict — /stats now matches: _job_owner(data) != principal.name. New test pins the behavior.

(2) Burst breakdown was blank when expanded

pollBurstAggregate set breakdown: {}. Now sums each job's breakdown fields (compute / memory / request / network) so the expanded view shows real per-component numbers across the burst.

(3) Top-5 (and by-language / by-status) overflowed horizontally

Wrapped each table in .tablewrap. The existing overflow-x: auto on that class now lets the table scroll inside the card instead of pushing it wide.

(4) Cost strip used a different surface token than .card

.card uses var(--card); strip used var(--surface). Same shadow / border / radius, but a slightly off shade. Matched to var(--card) so the strip blends visually with the cards next to it.

(5) Strip metrics changed between page refreshes

Hydration was picking "most recent terminal job", which shifted as background jobs completed. Estimated max and Actual could read different values second-to-second. Fix:

  • Persist the rendered estimate + final actual to localStorage.
  • Restore on boot before the first refreshAll, so the strip shows the last-seen values immediately on first paint.
  • Hydration stands down whenever persisted state exists.
  • Clear-finished resets the persisted state too — matches the user's "clearing also clears cost" expectation.

(6) Admin tab landing + sidebar order

  • Admin entry moved to the top of the sidebar.
  • Admins landing fresh (no explicit URL hash) go straight to #admin instead of #invoke.

Cost-calculation audit (single, burst, files, codebase)

Verified live, end-to-end, that every path produces the formula's math to 1e-9 precision:

cost = compute + memory + request + network
compute = duration_seconds × vcpu_share × per_vcpu_second
memory  = duration_seconds × (memory_mb / 1024) × per_gb_second
request = attempts × per_request
network = network_seconds × per_network_second
Path duration match
inline single 0.128s ✓ exact
with uploaded file 0.126s ✓ exact
codebase + entrypoint 0.139s ✓ exact
burst aggregate (5 jobs) sum ✓ exact

Test plan

  • pytest -q — 179 passed (1 new test for /stats scope).
  • Live cost-math verification across all 4 paths.

app.js?v=22, styles.css?v=16.

(1) /stats leaked other users' jobs to admins.
    `principal.can_view()` returns True for admins regardless of owner,
    so the recent-counts dashboard included every user's job. Switched
    to strict `_job_owner(data) != principal.name`, matching what
    /jobs and /usage/cost already do. New test pins the behavior.

(2) Burst breakdown was blank when expanded.
    pollBurstAggregate set breakdown to {} on purpose. Sum each job's
    breakdown fields (compute / memory / request / network) so the
    expanded view shows real numbers for the whole burst.

(3) Top-5 most expensive table overflowed horizontally.
    Wrapped it (and by-language and by-status) in .tablewrap so the
    existing overflow-x: auto kicks in and content can scroll within
    the card instead of pushing it wide.

(4) Cost strip used a different surface token than .card.
    .card uses var(--card); strip used var(--surface). Same shadow,
    same border, but a slightly off shade. Matched to var(--card) so
    the strip blends in visually with the cards next to it.

(5) Strip metrics changed between page refreshes.
    Hydration picked "most recent terminal job" — which can shift
    between refreshes if jobs completed in the background, so the
    Estimated max and Actual cells could read different values
    second-to-second. Fix: persist what the user last saw to
    localStorage (LAST_ESTIMATE_STORAGE / LAST_ACTUAL_STORAGE) and
    restore on boot before any refreshAll runs. Hydration now stands
    down whenever persisted state exists. Clear-finished resets the
    persisted state too.

(6) Admin sidebar: moved Admin to the top of the list, and admins
    landing fresh (no explicit hash) now go straight to #admin instead
    of #invoke.

Cost-calculation audit (single / burst / files / codebase): verified
the math against the formula end-to-end. All four paths produce
cost = compute + memory + request + network with values matching
duration × vcpu × per_vcpu_second + duration × gb × per_gb_second
+ attempts × per_request to 1e-9 USD precision.

Bumped app.js?v=22 + styles.css?v=16.
@aakri0
aakri0 merged commit 90dcf76 into main May 2, 2026
3 checks passed
@aakri0
aakri0 deleted the fix/cost-deep-fixes branch May 2, 2026 20:57
aakri0 added a commit that referenced this pull request May 3, 2026
The previous PR (#51) added an admin-default branch in showTabFromHash,
but it never fired: hideAuthGate() rewrites the URL hash from #login
to #invoke right before showTabFromHash() reads it, so the admin saw
'invoke' and went there. (And then auto-redirected to invocations
because that's where the URL took them on a follow-up nav.)

hideAuthGate now picks the landing hash by role: #admin for admins,
#invoke for everyone else. state.principal is set in bootstrapPrincipal
before hideAuthGate runs, so the role check is reliable.

Bumped app.js?v=23.
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