Commit Graph
2393 Commits
Author SHA1 Message Date
Bohdan Triapitsyn bf0dfc4e6b feat: add Command Code provider logo
Adds a new provider logo asset for Command Code
Uses the provider logo in third-party integrations when available
2026-08-18 16:24:30 +03:00
Bohdan Triapitsyn 17d08c0395 feat(quota): add Command Code usage tracking 2026-08-18 16:24:02 +03:00
Serhii Dziupin a01b7eec78 Merge pull request #2914 from pocharlies/fix/context-usage-server-total
fix(ui): stop the context meter from counting every internal round-trip
2026-08-18 11:07:04 +03:00
Bohdan Triapitsyn 34e8a24b20 feat(knowledge): rebuild the project notes panel as Project knowledge (#2973)
The panel stored notes, todos and plans inside one shared JSON file that
six unrelated domains also wrote to, synchronised itself through window
CustomEvents, and could only read plans. It is now Project knowledge:
server-owned storage with explicit routes, a store with rollback, a
section sidebar, plans that open and edit in place, and search across
all of it.

Notes and plans the user pins travel with every message sent in that
project. Pinning is project state, not an attachment to one message, so
it holds until unpinned and the work status panel names what is riding
along and can detach it.

Agent memory is added alongside, in two scopes: what is true about the
user, and what is true about this codebase. The split is not cosmetic —
a wrong project fact costs one project and is noticed, while a wrong
global fact quietly shapes every session everywhere and the user has no
code to check it against. It stays separate from notes so an agent
mistake cannot land in what the user wrote. Sessions receive an index of
titles only; bodies are read on demand, because an index carrying full
text grows until it crowds out the conversation.

Deciding what a session must be told, and whether it has been told, now
lives on the server. The client owned it before, which meant sessions
started without a UI — scheduled tasks, sessions the agent dispatches —
received nothing at all, and a tab's record of what it had sent outlived
the conversation: after compaction the agent no longer held the block
while the tab went on believing it did. What was delivered is recorded
in the session's own metadata, and compaction restores it through the
runtime that already restores pinned messages, in the same turn.

Agent memory ships dark behind OPENCHAMBER_MEMORY_ENABLE: unset, there
is no tool, no routes, no session index, no settings row and no panel
tab. Absent rather than switched off, so nothing invites turning on a
feature that has not been announced. Pinned notes and plans are
unaffected and ship as normal.
2026-08-18 02:59:04 +03:00
Aaron Hogue 7611076436 fix(proxy): reuse upstream connections for OpenCode API requests (#2916)
* fix(proxy): reuse upstream connections for OpenCode API requests

`createProxyMiddleware` was constructed without an `agent`, so `http-proxy`
fell back to `agent: false`. That disables connection pooling and forces
`Connection: close` on every proxied request, consuming one ephemeral port
per request.

Measured against a real `opencode serve` instance, 200 sequential requests
through the proxy created 201 TIME_WAIT entries (1.005 ports/request). With
a keep-alive agent the same load creates 0.

On macOS the ephemeral range is 16,384 ports and TIME_WAIT lasts 30s, so
sustained traffic around 546 req/sec exhausts the pool — after which every
process on the host fails to open outbound connections with EADDRNOTAVAIL.

`maxSockets: Infinity` preserves the unbounded concurrency of `agent: false`,
so this changes connection reuse only, not request throughput.

Partially addresses #2915.

* fix(proxy): derive proxy agent class from the target scheme

Addresses review feedback on #2916. The first commit created an
unconditional `http.Agent`, which regresses external OpenCode servers
configured over https via `OPENCODE_HOST` (accepted by env-config.js).

http-proxy dispatches through `https.request` when the target protocol is
`https:` (http-proxy/lib/http-proxy/passes/web-incoming.js:126), and
`http.Agent#createConnection` is plain `net.createConnection` — so an
http.Agent would open a plaintext socket to a TLS port and fail every
proxied request. `agent: false` previously worked for both schemes.

`createOpenCodeProxyAgent(target)` now returns an `https.Agent` for https
targets and an `http.Agent` otherwise, derived once from
`resolveProxyTarget()` at registration so the single shared instance is
preserved across `apiProxy` and `interactiveOAuthProxy`.

Guarded in both test layers, verified to fail when the selection is
reverted to an unconditional http.Agent. `https.Agent` extends
`http.Agent`, so the http cases assert `not.toBeInstanceOf(https.Agent)`.

* Round 2: fix: resolve the proxy agent lazily so cold starts honor https

Addresses the round-2 blocker on #2916. Deriving the agent class at
registration is too early: startup-pipeline-runtime.js calls setupProxy()
(line 104) before bootstrapOpenCodeAtStartup() (line 141), so on a fresh
process state.openCodePort is null, buildOpenCodeUrl() throws
(network-runtime.js:86-88), and resolveProxyTarget() returns the http
loopback fallback. An external server configured via OPENCODE_HOST=https://
only appears on state.openCodeBaseUrl after bootstrap, so it was still
getting a plain http.Agent — the regression the previous commit intended
to fix.

`agent` is now a getter backed by a per-scheme memoizing resolver.
http-proxy-middleware rebuilds per-request options with
`Object.assign({}, this.proxyOptions)` in prepareProxyRequest, which invokes
getters, so resolution happens at request time while still yielding one
shared pool per scheme.

Tests now model the production ordering — registration while the port is
null and buildOpenCodeUrl throws, then an https base URL appearing after
bootstrap — and fail against the eager implementation. A behavioral test
pins the http-proxy-middleware option re-read the fix depends on, so a
library change that froze options would fail loudly instead of silently
regressing https targets.

The resolver is module-private; `bun run dead-code` flagged it as an
unused export when it was exported.

* Round 3: docs(changelog): note upstream connection reuse under [Unreleased]

Repo precedent adds [Unreleased] bullets for comparable proxy/stability
fixes (1.18.4 Stability, 1.9.3 Reliability/Proxy). Non-blocker raised in
review on #2916.

* Round 3: docs(changelog): use repo-standard 'behavior' spelling

* Round 4: docs(changelog): don't imply a restart is the only recovery

The ephemeral port pool drains on its own once the exhausting traffic
stops (TIME_WAIT expiry), so a restart is sufficient but not necessary.
Optional nit raised in review on #2916.

* Round 5: fix: construct the proxy agent through one factory; widen the pool

Review found the https branch was mutation-uncovered: the resolver
re-implemented agent construction inline instead of calling the exported
`createOpenCodeProxyAgent(target)`, so replacing its https branch with
`new https.Agent()` — dropping OPENCODE_AGENT_OPTIONS, and with it
keep-alive — left the entire suite green. Since `createOpenCodeProxyAgent`
also had no production callers, its four tests were pinning dead code.
Delegating collapses both: the factory is now the single construction
path, and the mutation fails 2 tests including the live resolver path.

Also from review:

- maxFreeSockets 32 -> 256 (Node's own default). The lower cap evicted
  pooled sockets under concurrency, reintroducing the churn this agent
  exists to prevent: at 64 concurrent requests it left 303 sockets in
  TIME_WAIT versus 0 at 256.
- Added `timeout` to OPENCODE_AGENT_OPTIONS. Free-socket eviction is
  governed by agent.options.timeout, which was unset, so idle sockets
  persisted until the peer closed them. `keepAliveMsecs` is the TCP probe
  delay, not the idle lifetime.
- resolveProxyTarget() now checks openCodePort before calling
  buildOpenCodeUrl instead of relying on it throwing. The port is nulled
  on several runtime paths (health-check failure, failed restart), so a
  degraded OpenCode made every proxied request pay for a thrown-and-caught
  exception — and the getter added a second call per request.
- Test fixtures use :4096 rather than :443; WHATWG URL elides the default
  port, so parseInt('') is NaN and env-config rejects that host. The
  fixtures modeled a state that cannot reach production.
- The getter-read assertion is now exact (0 at construction, 1, then 2)
  rather than >= 2, which would have passed if the getter were read twice
  at construction and never per-request.
- listen() rejects on 'error' and servers start inside try/finally, so a
  bind failure fails the test instead of hanging to timeout.
2026-08-17 23:44:38 +03:00
Serhii Dziupin d9f1c0d44c Merge pull request #2800 from openchamber/feat/shiki-re-highlighting-performance-dd3a
fix(ui): stop sustained Shiki re-highlight of unchanged markdown (#2769)
2026-08-17 17:15:07 +03:00
Serhii Dziupin 69150366fd fix(ui): correct markdown cache identity, streaming churn, and redundant tiers
Review follow-up on the #2769 highlight caches.

Fingerprint strength. The block/highlight caches are now global and
content-addressed, so a hash collision no longer mis-colors a block — it returns
a *different* block's rendered HTML and shows the user source they never wrote.
Length + one 32-bit FNV-1a is not enough key space for that failure mode at
session scale. `contentFingerprint` now combines two independent 32-bit
multiplicative hashes with a final avalanche (~64 bits); two multiplies per
character are free next to Shiki tokenization.

Streaming churn. Content addressing made every streaming step of the trailing
`live` block insert a new cache entry, so one long message evicted the settled
`full` blocks the fix exists to keep warm. `full` and `live` blocks now use
separate caches; the live cache is small (32 entries / 2MB) because it only has
to absorb repeat renders of the same step.

Redundant worker-side caches. `markdown-worker.ts` is the only sender to the
Shiki worker, and its client cache is larger than the worker-side ones, so the
worker caches could not serve a hit the client had not already served — they
only duplicated up to 48MB of payloads in a second heap. Removed; the reason
memoization belongs on the client is now documented there, along with why only
`highlightTokens` carries a theme in its key.

Dead `cacheKey` plumbing. `renderMarkdownBlocks` kept a `cacheKey` parameter it
only `void`-ed. Removed it and the now-unused `useMorphdomMarkdown` prop; the
remaining call-site local is renamed `fadeKey` for what it actually keys.

Tests: image-mode cache identity, streaming-does-not-evict-settled-blocks,
live-cache reuse, and a 20k same-length-source fingerprint collision check.
Each new guard was verified to fail without its fix.
2026-08-17 16:45:53 +03:00
Serhii Dziupin 6d6ece6856 fix(config): fail closed when config content yields no JSON value
Treating every undefined parse as empty config let a file that is not JSON
at all (YAML, plain text) read as {}, so a later write would back it up and
replace it - the same data loss this fix is meant to prevent. Only a
comment-only parse, where ValueExpected is the sole error, counts as empty.
2026-08-17 16:44:17 +03:00
Serhii Dziupin fc0ae0f445 Merge upstream/main into feat/shiki-re-highlighting-performance-dd3a
Conflict: packages/ui/src/components/chat/markdown/markdownCore.ts

main added per-image-mode markdown parsers (`imageMode` threaded through
`parseBlock` and into the block cache key); this branch replaced the
identity-keyed block cache with a content-addressed LRU. Resolution keeps the
content-addressed cache and folds `imageMode` into the content key, so the
`inline` and `label` renderings of the same source cannot answer for each other.
2026-08-17 16:44:05 +03:00
Serhii Dziupin 07f8264e72 merge: resolve changelog conflicts with main
Keep main's reordered Unreleased lists and add the config-wipe fix bullet
at the top of both changelogs.
2026-08-17 16:29:08 +03:00
Serhii DziupinandBohdan Triapitsyn 1c76dbefe4 fix(chat): defer composer value writeback during IME composition (Fixes #2527) (#2691)
* fix(chat): defer composer value writeback during IME composition

The controlled-writeback effect compared the value prop against the
CodeMirror document and, on mismatch, dispatched a wholesale replacement
with the caret forced to the end. While the browser composes (pinyin,
kana, hangul) the uncommitted text lives in the DOM, not in the document,
so the mismatch is expected and the dispatch interrupted the IME session
and jumped the cursor. Skip the writeback while the view is composing,
using CodeMirror's public compositionStarted getter; the composition
commits through its own pipeline and reports via onChange.

Fixes #2527

* fix(chat): preserve external composer writes during IME

* fix(chat): restore composition-wide writeback guard

---------

Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
2026-08-17 14:24:39 +03:00
Bohdan Triapitsyn 7ef6441bf3 Reduce anti-slop findings in Persistence (#2953)
* chore(ui): reduce persistence anti-slop findings

* test(ui): cover fallback settings response

* fix(ui): preserve usage model group contract
2026-08-16 19:21:35 +03:00
Bohdan Triapitsyn 58c190f0b1 fix(web): use Vitest timers in PR status tests 2026-08-16 17:30:45 +03:00
Bohdan Triapitsyn 21152ec120 chore(vscode): update changelog for integrations settings page 2026-08-16 15:55:49 +03:00
dibanez 450d30b3ee docs(changelog): credit the context meter fix contributor
Requested by review: the changelog-authoring skill requires inline
contributor credit for non-owner contributors.
2026-08-15 21:47:02 +02:00
dibanez 9e1a9b59b1 fix(ui): stop the context meter from counting every internal round-trip
The token breakdown of an assistant message accumulates across every API
round-trip inside the turn: each tool call re-reads the whole cached
prompt, so input/cache.read add up to several times the context window.
Every context-usage surface summed those fields, which is why the meter
could read 330% of a 1M window whose real fill was 232,872 tokens
(23.3%), and why reopening an older session jumps the readout (#2562).

The server reports the final round-trip's window as tokens.total
(optional in the message schema; opencode 1.18.18 returns it, verified
against its live /session/:id/message API). Prefer it everywhere the
window fill is displayed and fall back to summing only when the server
did not send it: contextTokensFromBreakdown in tokenUtils now owns that
rule, and the context store extractor, sync store getter, work status
panel, context sidebar, VS Code layout, mini chat, and mobile metadata
all use it instead of their own inline sums.

Fixes #2562
2026-08-15 21:46:49 +02:00
Bohdan Triapitsyn e3094ee676 docs(changelog): add pending unreleased entries and reorder by impact 2026-08-15 17:58:31 +03:00
Bohdan Triapitsyn 268f9ea9f2 fix(github): keep merged PRs as branch history instead of hiding them
Branch status resolves an open PR across the whole fork network first, so a
merged fork PR can never hide an open upstream PR for the same head. Only when
no target has an open PR does the branch's newest closed/merged PR come back,
as history.

The panel shows that history as a compact note and offers creating the next PR
below it, instead of either sticking on a terminal PR or going blank after a
merge. Terminal associations stay persisted for reload continuity but are never
treated as authority: they revalidate on the discovery cadence and on focus.

History is looked up only for the branch's own remote and name, and remembered
per repo+branch, so the extra lookup cannot exhaust the route's resolve budget.
The checks summary and merge-permission lookup are skipped for a closed or
merged PR, where neither is actionable.
2026-08-15 17:56:55 +03:00
Bohdan Triapitsyn 52ac367b1e feat: update annotate toolbar icon to markup 2026-08-15 16:33:07 +03:00
Serhii Dziupin bfcb555aa5 Merge pull request #2928 from openchamber/feat/stale-last-directory-fallback-1be0
fix(sessions): recover new chats from a deleted lastDirectory
2026-08-15 15:48:25 +03:00
Bohdan Triapitsyn 9032dfa5c0 fix(ui): show project names exactly as the folder is named
Auto-derived project labels were title-cased, turning .ssh into .Ssh and
opencode-claude into Opencode Claude. Show the folder name verbatim in the
sidebar, window title, settings selector and notification templates, and
migrate persisted legacy labels back to the folder name (manual renames
are preserved).
2026-08-15 10:16:31 +03:00
Cursor Agentandserkraser d817c44c46 fix(sessions): accept in-flight draft rewrite to the fallback directory
Create no longer aborts when recoverStaleDraftDirectory rewrites the
implicit new-chat draft to the active project during the create probe.
Also rank the Unreleased Chat bullet below the changelog highlights.

Co-authored-by: serkraser <serkraser@gmail.com>
2026-08-15 06:33:28 +00:00
Cursor Agentandserkraser 6751c7dc7a fix(config): isolate plugin list reads from a broken JSONC layer
Plugin listing still called readConfigFile per layer, so one unparseable
project file made GET /api/config/plugins and VS Code listPluginEntries
fail. Reuse readConfigLayer isolation and pin comment-only empty parse
in the VS Code suite.

Co-authored-by: serkraser <serkraser@gmail.com>
2026-08-15 06:30:36 +00:00
Cursor Agentandserkraser 8b086343fd merge: resolve changelog conflicts with main
Keep both Unreleased bullets: the config-wipe Settings fix and the
stale merged-PR Git panel fix from main.

Co-authored-by: serkraser <serkraser@gmail.com>
2026-08-15 05:30:54 +00:00
Cursor Agentandserkraser 35563dd78d fix(config): isolate broken JSONC layers and treat comment-only as empty
Address review findings on the #2923 fail-closed parse fix:

- Comment-only files produce ValueExpected with no JSON value; treat that
  as empty config instead of INVALID_JSONC. Partial object trees still throw.
- readConfigLayers no longer lets one unparseable layer abort valid sibling
  layers. Mutations still fail closed on the custom/user write target.

Co-authored-by: serkraser <serkraser@gmail.com>
2026-08-15 05:13:32 +00:00
Cursor Agentandserkraser a500a5d4e9 merge origin/main into feat/stale-last-directory-fallback-1be0
Keep both Unreleased changelog bullets: the lastDirectory new-chat
fallback and the Git PR panel stale-status fix from main.

Co-authored-by: serkraser <serkraser@gmail.com>
2026-08-15 05:10:00 +00:00
Serhii Dziupin 8c91c82b62 Merge pull request #2929 from openchamber/feat/fix-stale-merged-pr-status-3a1e 2026-08-15 08:07:14 +03:00
Serhii Dziupin 11a136bba8 Merge pull request #2913 from Gautam0507/fix/2803-session-retention-persist 2026-08-15 08:05:48 +03:00
Serhii Dziupin 13b25eea64 Merge pull request #2920 from alohaninja/fix/ui-unused-runtimefetch-import 2026-08-15 08:03:17 +03:00
Cursor Agentandserkraser 7db8b2c463 chore(ui): keep directory availability type module-private
The availability union is only used by the OpenCode client probe.

Co-authored-by: serkraser <serkraser@gmail.com>
2026-08-15 04:47:15 +00:00
Cursor Agentandserkraser ed972d9f9e test(ui): use toBe(undefined) for persist/hydrate assertions
The UI expect helper does not type toBeUndefined.

Co-authored-by: serkraser <serkraser@gmail.com>
2026-08-15 04:30:30 +00:00
Cursor Agentandserkraser 9090733908 fix(sessions): rewrite stale new-chat drafts before send
When a regular new-chat draft inherits a deleted lastDirectory, update
the visible draft target to the active project immediately. lastDirectory
still stays unchanged until session creation succeeds.

Co-authored-by: serkraser <serkraser@gmail.com>
2026-08-15 04:28:07 +00:00
Cursor Agentandserkraser c1640522ec docs(changelog): note stale merged PR panel fix
Co-authored-by: serkraser <serkraser@gmail.com>
2026-08-15 04:27:52 +00:00
Cursor Agentandserkraser 13cbbc76d7 test(github): cover persist, hydrate, and complete open-list misses
Add store persist/hydrate regressions for terminal branch associations
and a server test that a complete empty open list does not query closed
PRs. Inline the open-only matcher state so the next repo target can win.

Co-authored-by: serkraser <serkraser@gmail.com>
2026-08-15 04:27:52 +00:00
Serhii DziupinandSerhii Dziupin 5e9e35897f fix(github): harden terminal PR revalidation after critical review
Recompute focus/visibility staleness at event time, retry closed/merged
sidebar associations on the no-PR cadence, and assert refresh failures
preserve prior PR status.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
2026-08-15 04:27:02 +00:00
Serhii DziupinandSerhii Dziupin 962b016cbd fix(github): stop stale merged PRs from sticking in branch status
Branch PR status now resolves open PRs only, revalidates closed/merged
associations on a discovery cadence, and clears authoritative empty
results so the panel can self-heal without a manual refresh.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
2026-08-15 04:27:02 +00:00
Cursor Agentandserkraser 90e79b04a4 fix(sessions): fall back when lastDirectory is a deleted worktree
New chats inherited a persisted lastDirectory even after that worktree
was removed, so the first message saved but the prompt never started.
Validate the implicit draft directory, fall back to the active project
only when OpenCode confirms the path is missing, and leave explicit
worktree targets and unknown probes unchanged.

Co-authored-by: serkraser <serkraser@gmail.com>
2026-08-15 04:26:39 +00:00
Cursor Agentandserkraser 4cc090130c fix(config): refuse partial JSONC parses that wipe opencode.jsonc
jsonc-parser was returning truncated trees (often only `$schema`) when
configs contained JSON5-style unquoted keys. Config mutations then backed
up and overwrote the full file with that stub. Check parse errors on read
and refuse to overwrite unparseable files on write in web and VS Code.

Fixes #2923

Co-authored-by: serkraser <serkraser@gmail.com>
2026-08-15 04:25:40 +00:00
Serhii Dziupin 6b1e677aaf Merge pull request #2925 from makeittech/feat/fix-issue-2903-embedded-subagent-2cbc
fix(ui): restore embedded subagent history (#2892, #2903, #2919, #2922)
2026-08-15 07:07:39 +03:00
Serhii Dziupin 4e6ac40801 fix(ui): load embedded history while visibility stays inactive
Keep session-message loads and retries on messagesEnabled so a mounted
session-chat panel can materialize history even before the visibility
handshake, and cover the enabled-gate with the real hook.

Co-authored-by: serkraser <serkraser@gmail.com>
2026-08-15 03:51:33 +00:00
Serhii Dziupin 11537de734 Merge pull request #2924 from makeittech/feat/fix-clawhub-label-typo-5c30
Fix ClawHub display name typo in Skills Catalog (#2895)
2026-08-15 06:43:56 +03:00
Serhii DziupinandSerhii Dziupin de0455e10e test(ui): drop tautological #2903 enabled-gate helper
The helper reimplemented `if (!enabled) return []` locally, so those
cases never exercised the real hook. Keep the snapshot-builder and
source-contract coverage instead.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
2026-08-15 03:42:45 +00:00
Serhii DziupinandSerhii Dziupin 0a07bc7e03 test(ui): assert #2903 records through the real snapshot builder
Replace the fake enabled-gate helper's store fixture with
buildSessionMessageRecordsSnapshot so the regression covers the same
record shape ChatContainer renders.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
2026-08-15 03:40:37 +00:00
Serhii DziupinandSerhii Dziupin 903638db94 fix(ui): show embedded subagent history while visibility stays inactive
Busy context-panel session chats could render only the working-status row
when the iframe booted inactive or lost its visibility handshake, because
message reads shared the composer/background-work gate. Keep message
subscriptions enabled in the mounted session-chat panel so materialized
history remains visible (#2903, #2892).

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
2026-08-15 03:40:37 +00:00
Aaron Hogue bc443bba82 fix(ui): drop unused runtimeFetch import in gitApi
d353638f3 replaced both `runtimeFetch('/api/small-model/generate')` calls
with `requestSmallModel` but left the original import in place, so
`@typescript-eslint/no-unused-vars` fails `bun run --filter '*' lint` on
main:

  packages/ui/src/lib/gitApi.ts
    5:10  error  'runtimeFetch' is defined but never used

This blocks CI for every open PR, since pull_request runs lint against the
merge with main.
2026-08-14 19:41:49 -04:00
Bohdan Triapitsyn 1060ad1913 fix(ui): use native collapsible integration cards 2026-08-15 02:02:51 +03:00
Bohdan Triapitsyn d353638f39 fix: integrate Claude CLI provider state 2026-08-15 01:59:26 +03:00
Gautam0507 b15b20fe08 fix(settings): persist sessionRetentionAction through the settings sanitizer 2026-08-15 00:39:36 +05:30
Serhii DziupinandSerhii Dziupin a216b6871f Fix ClawHub display name typo in Skills Catalog (#2895)
The Skills Catalog source dropdown showed "ClawdHub"; the registry brands
itself as ClawHub. Update curated/fallback labels across web, UI, and VS Code,
align docs, and add regression tests.

Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
2026-08-14 18:49:31 +00:00
Bohdan Triapitsyn 72cd861eb8 fix(quota): match the Claude Code integration provider to Claude quota
The opencode-claude integration registers its provider as `claude-code`, which
never matched the `claude` quota provider, so the collapsed Usage section in
the work status panel showed no limit for a model from that integration.

Claude windows also reported no duration, leaving the headline to fall back to
whichever row came first instead of the limit that runs out soonest. The
session and weekly windows now carry their length; extra usage stays without
one because it is a monthly spend cap.
2026-08-14 21:07:55 +03:00