fix: /protocol leftovers — liquidity 24h-only Δ% + denser UTC volume x-axis labels #677

Closed
opened 2026-08-27 00:17:40 +00:00 by PlasticDigits · 14 comments
PlasticDigits commented 2026-08-27 00:17:40 +00:00 (Migrated from gitlab.com)

Summary

/protocol Global stats still has two leftover display bugs after #667 / #668:

  1. Total liquidity shows 24h and 30d Δ% in the same box (+7.782% 24h — 30d). Operators asked for 24h change only.
  2. UTC volume x-axis does not label every column. sparseTimeLabelIndexes(..., maxLabels = 5) is the intended cap today. Phone Daily (7 bars) already skips columns (e.g. 08-20, gap, 08-22…). Same skip on Hourly and Monthly. Prefer a label on every bar; every second bar is acceptable if labels would wrap/collide.

Bundle into one frontend issue. Do not split liquidity vs chart — both are /protocol Global stats leftovers from the same QA pass.

Related: #667 P667-2 (explicitly kept 24h+30d on liquidity), #668 P668 (axis exists; density was never specified), #652, #569 P569-1, #576 (trailing tiles ≠ calendar bars), #653, #489.

Current codebase

Both leftovers are implemented as specified, not missing widgets. Tests encode the old product choice, so make verify-issue-667 / 668 stay green while production still looks wrong.

Layer Behavior today
Liquidity tile ProtocolGlobalStats.tsx STATS[0] passes two deltas: liquidity_change_24h_pct (protocol-stat-liquidity-24h, label 24h) and liquidity_change_30d_pct (protocol-stat-liquidity-30d, label 30d). One cell, one .stat-delta-cluster. Lead copy still says “24h/30d liquidity is vs indexer snapshots.”
Volume / fee tiles Unchanged and correct: Last 24h / 7d / 30d vol (and fees) each own one window + one Δ%. Do not strip those 30d tiles.
StatBox StatBox.tsx already groups chips with the $ (justify-start / wrap, #667). Comment still says “Prefer deltas for liquidity 24h+30d.”
Overview JSON Additive liquidity_change_24h_pct and liquidity_change_30d_pct stay on GET /overview (types/index.ts). Snapshot stock % (P569-2).
Chart labels ProtocolVolumeDailyChart.tsx builds xLabels from sparseTimeLabelIndexes(series.length) and draws SVG <text> under protocol-volume-chart-xaxis. Tooltip / aria-label already have the full UTC period.
Sparse helper protocolVolumeGrain.ts sparseTimeLabelIndexes(count, maxLabels = 5): if count ≤ 5 label all; else exactly 5 evenly spaced indexes (first + last inclusive). Comment: “do not label every hourly bar.” Daily min is 7, monthly min 6, hourly min 12 — all three grains therefore skip columns on the default phone plot.
Axis format formatPeriodAxisLabel: hourly → HH (14), daily → MM-DD (08-26), monthly → YYYY-MM. Fixed viewBox 320×128, PAD_B = 22, fontSize="8".
Tests that lock the leftover ProtocolPage.test.tsx asserts protocol-stat-liquidity-30d text. e2e/protocol-page.spec.ts requires the 30d chip visible + grouped (desktop / tablet / phone). protocolVolumeGrain.test.ts asserts sparseTimeLabelIndexes(168).length ≤ 5.
Invariants P569-1: “USD + inline 24h/30d snapshot Δ%.” P667-2: “Liquidity keeps 24h+30d in one .stat-delta-cluster.” P668-1–P668-8: USD Y-axis + tooltip + grain tabs; no x-label density rule.

Why this is needed

  1. Two windows in one liquidity box is a retail accounting error. Volume already has a dedicated Last 30d vol tile. Putting — 30d next to the 24h chip on Total liquidity reads as “this TVL moved 24h and 30d,” then the 30d chip is often an em-dash (window not filled). Operators asked for 24h only in that box.
  2. #667 shipped the dual-chip on purpose. P667-2 / P569-1 must be amended, not treated as a regression. Leaving the 30d chip is not a grouping leftover — it is the wrong product.
  3. Five x-labels is too sparse for the default Daily plot. Seven UTC days with two unlabeled bars looks broken (08-20 … 08-22). Monthly (6–24) and Hourly (12–168) inherit the same maxLabels = 5 helper, so the skip is not Daily-only.
  4. Tooltip is not a substitute for the axis. Pointer/keyboard tooltip (P668-2) already shows the period. Operators still need to read the series without hovering every bar — especially on phone.

Constraints / guardrails

  • Frontend-only. Do not change overview JSON, snapshot SQL, flow_change_pct, grain GET allowlist, rollup tables, or wasm. Keep liquidity_change_30d_pct on the wire (additive / old clients). Stop rendering it on Total liquidity.
  • Do not remove Last 30d vol / Last 30d fees. Those tiles stay. Only the liquidity cluster drops 30d.
  • Keep #667 grouping. Single remaining liquidity chip (protocol-stat-liquidity-24h) still sits immediately after the $ (justify-start / wrap). Do not restore justify-between.
  • Keep #653 chrome. Flat StatBox inside shell-panel. No nested card-glass. No PriceChart.
  • Keep #668 grain contract. Tabs Hourly / Daily / Monthly (default Daily). limit still follows plot width + grain [min, max]. Client still rejects non-allowlisted grain/limit before fetch. Hide chart on 404/501 (P652-6).
  • Do not SUM swap_events or N+1 Llama to invent extra axis ticks.
  • Label density floor. Daily and Monthly: step 1 (every bar) or step 2 (every other) only. Hourly may use a wider step only when a step-2 label would collide in the fixed viewBox (168 bars × MM/HH). Do not keep a global maxLabels = 5.
  • First and last bar stay labeled when any labels are shown.
  • Copy / a11y. Labels ≤ ~5 words (#489). Keep TRAILING_LIQUIDITY_24H_TITLE + composeStatAriaLabel on the 24h chip. Tooltip stays text (no innerHTML / eval / javascript:). XSS period / volume_usd still render as text.
  • Δ% semantics unchanged. Liquidity 24h stays snapshot stock % (P569-2). Missing / null / non-finite → em-dash. Never Infinity / fake 0%.
  • Do not lecture. No new Global stats banner. Trailing vs UTC-calendar copy stays #576.
  • Update invariants. Amend P569-1 and P667-2 (24h-only on liquidity). Add an x-axis density rule on P668 (or a new P6xx on the same skill). docs/frontend.md § Protocol. New make verify-issue-*. Keep verify-issue-667 / 668 / 652 / 550 / 569 / 586 / 653 / 576 green after those scripts are updated to the new product (do not leave them asserting the 30d liquidity chip or ≤ 5 x-labels).
  • No wallet / signing / localStorage for this census panel.

Relevant files

Path Role
frontend-dapp/src/components/protocol/ProtocolGlobalStats.tsx Drop the 30d liquidity deltas entry; keep 24h
frontend-dapp/src/components/ui/StatBox.tsx Comment / deltas still valid for one chip; no layout revert
frontend-dapp/src/utils/trailingWindowCopy.ts Keep 24h liquidity title; 30d liquidity title unused on this tile
frontend-dapp/src/utils/protocolVolumeGrain.ts Replace maxLabels = 5 with grain/width-aware step 1 or 2
frontend-dapp/src/components/protocol/ProtocolVolumeDailyChart.tsx Pass grain (and slot/width) into the label picker; keep tooltip
frontend-dapp/src/utils/__tests__/protocolVolumeGrain.test.ts Daily 7 / monthly 6 label all (or every 2nd); drop 168 ≤ 5
frontend-dapp/src/pages/ProtocolPage.test.tsx 24h chip present; no protocol-stat-liquidity-30d in the tile; x-axis text count
frontend-dapp/src/components/ui/__tests__/StatBox.test.tsx Multi-deltas still allowed for other callers; liquidity page no longer uses two
frontend-dapp/e2e/protocol-page.spec.ts Stop requiring liquidity-30d; assert 24h-only + x-axis density on Hourly/Daily/Monthly
skills/AGENTS_FRONTEND_PROTOCOL_STATS.md Amend P569-1 / P667-2; add P668 x-label density
docs/frontend.md Protocol census: liquidity 24h-only; chart labels every bar or every 2nd
New scripts/qa/verify-issue-NNN.sh + make verify-issue-NNN Docs grep + Vitest + Playwright (e2e-smoke @ 5 workers)
  1. Liquidity: in ProtocolGlobalStats STATS liquidity deltas, keep only the 24h object. Remove protocol-stat-liquidity-30d from this tile. Leave liquidity_change_30d_pct on IndexerOverview unused here. Soften the lead sentence from “24h/30d liquidity” to “24h liquidity is vs indexer snapshots” (still not on-chain genesis).
  2. Do not invent a second liquidity tile for 30d. Last 30d vol already covers the long window; TVL 30d stays available to API consumers.
  3. X-axis: replace sparseTimeLabelIndexes(count, 5) with a helper that takes count, grain, and plot/slot width. Default step 1. If the formatted label width (MM-DD / HH / YYYY-MM at fontSize 8) exceeds the slot, use step 2. Hourly at high limit may compute step = ceil(labelPx / slotPx) but Daily/Monthly must stay 1 or 2.
  4. Collision: textAnchor="middle"; optional extra PAD_B if daily MM-DD wraps. Do not rotate the chart into a horizontal USD category plot (P668-1).
  5. Tests: RTL — liquidity tile has 24h chip, queries 30d chip = 0. Grain unit tests — 7 daily indexes [0..6]; 6 monthly all or every 2nd; hourly 12 all or every 2nd; hourly 168 is width-clamped, not hard-capped at 5. Playwright — Hourly / Daily / Monthly: protocol-volume-chart-xaxis text count ≥ ceil(barCount / 2) and ≤ barCount.
  6. Verify script modeled on scripts/qa/verify-issue-667.sh / 668.sh. Update those scripts’ assertions that still require the 30d liquidity chip or ≤ 5 labels.

Acceptance criteria

  • Total liquidity shows USD + one Δ% chip: protocol-stat-liquidity-24h (label 24h). No 30d text and no protocol-stat-liquidity-30d inside protocol-stat-liquidity.
  • Missing / null 24h Δ% → em-dash. Never Infinity / fake 0%. #667 grouping still holds (chip with $, not justify-between).
  • Last 24h / 7d / 30d vol tiles and fee 24h / 7d / 30d tiles unchanged (30d stays on those boxes).
  • UTC volume Daily default: every bar labeled, or every second bar if a collision test proves step 1 overflows. No unlabeled “orphan” columns while neighbors are labeled at step > 2 on Daily/Monthly.
  • Hourly and Monthly use the same density rule (not Daily-only).
  • Phone 390 / tablet 820 / desktop 1280, light + dark: labels readable; first + last period visible when the series is non-empty.
  • Tooltip + aria-label still show full UTC period + USD (unpriced → em-dash). XSS strings stay text.
  • Overview keys, grain GET, #653 chrome, #576 trailing copy, page order (stats → fees → hub → oracle) unchanged.
  • P569-1 / P667-2 / P668 docs + skill amended. make verify-issue-NNN green.

Test plan — all paths

Path How
Liquidity happy Fixture with 24h +7.782 and 30d 12.5: tile shows +7.782% 24h only. queryByTestId('protocol-stat-liquidity-30d') is null.
Liquidity em-dash liquidity_change_24h_pct null / missing / non-finite → — 24h. 30d key present or absent must not resurrect a 30d chip.
Idle TVL $0 $0 + null 24h → $0 + —; no 0% / Infinity.
Old overview Missing liquidity_change_* → 24h em-dash; tiles still render (P652-6).
Volume / fees 30d protocol-stat-volume-30d / protocol-stat-fees-30d still visible with their own Δ%.
#667 geometry Desktop 1280 / tablet 820 / phone 390: remaining 24h chip chip.right ≤ tile.right and not under Last 24h vol.
Daily 7 bars (phone) sparse/labelIndexes(7, daily) is all 7 or [0,2,4,6]. SVG x-axis text count matches. No two-apart skip like today’s 5-label spread.
Daily 90 (wide) Step 1 or 2 only. First + last labeled.
Hourly 12 All or every 2nd.
Hourly 168 Step from slot vs label width; not forced to 5. Tooltip still works on unlabeled bars.
Monthly 6 / 24 Step 1 or 2. YYYY-MM must not overflow into the next label’s box at step 1 — then step 2.
Grain switch Click Hourly / Daily / Monthly: density rule reapplies; limit reset + ResizeObserver debounce unchanged.
Empty / all-null / 404 Empty copy or hide chart; no leftover x-labels.
Unpriced bar Outlined, not $0; axis label still the period.
Loading / error Skeleton / RetryError; no invented ticks.
Light / dark Axis fill stays --ink-dim; bars --accent.
Playwright Extend e2e/protocol-page.spec.ts; --project=e2e-smoke --workers=5.

Test plan — attack, hack, and abuse

Display-only leftover. Do not re-open fee/volume/TVL math. Fail closed on mis-attribution and injection.

Vector Expect
XSS in overview / series liquidity_change_24h_pct, utc_day / utc_hour / utc_month, volume_usd with <script>, javascript:, <img onerror> render as text or em-dash. No innerHTML. No style= / className from API strings.
Axis label injection formatPeriodAxisLabel slices trusted ISO-like keys. A hostile utc_day must not become an SVG <a href> or event handler.
Wrong-window social engineering A leftover — 30d on Total liquidity is the abuse (operators read a filled 30d TVL move that is not this tile’s job). RTL/e2e must fail if that chip returns.
Sparse-axis social engineering Unlabeled bars between labeled dates can be read as “no volume that day.” Density tests are the control. Tooltip must still tell the truth on skip-2 hourly bars.
Infinity / 1e309 / NaN Em-dash on Δ% and axis USD. Never Infinity% or NaN ticks.
Unpriced treated as $0 Unpriced stays outlined + tooltip —. Do not paint a $0 bar then a green “flat” story.
Query injection Grain/limit allowlist unchanged. from/to still 400. Extra query junk must not bust the 60s cache (P668-5).
DoS No new GET. No swap_events SUM. No Llama N+1. Resize debounce stays. Label helper is O(n) over the already-capped series.
Privilege / wallet /protocol is public census. No connect prompt, no signing, no localStorage of Δ% or grain (grain may stay React state only).
Open redirect / ticker Unchanged P550-2. Axis CSS must not depend on ?ticker=.
Hit-slop Long MM-DD / YYYY-MM labels must not cover the neighbor bar’s focus target.

Verification criteria

Done when:

  1. make verify-issue-NNN (new script) is green: skill/docs say liquidity 24h-only and x-axis step 1 or 2; Vitest covers both; Playwright e2e-smoke @ 5 workers checks the 30d liquidity chip is gone and Hourly/Daily/Monthly x-label counts meet the floor.
  2. make verify-issue-667 668 652 550 569 586 653 576 stay green after their assertions are updated to the new product (no leftover “must show liquidity-30d” / “≤ 5 x-labels”).
  3. Manual /protocol at 390 / 820 / 1280, light + dark: Total liquidity has one 24h chip; Daily/Hourly/Monthly show a label on every bar or every second bar.
  4. No overview / indexer / wasm diff required for merge.

Out of scope

  • Changing snapshot vs flow Δ% math, grain GET, or hub/oracle cards.
  • Adding a 30d liquidity tile elsewhere.
  • Flattening chrome again (#653).
  • Unique-trader headline / lecture banner (#489 / P550).
  • DeFiLlama range dump (#631 / P652-3).
## Summary `/protocol` **Global stats** still has two leftover display bugs after [#667](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/667) / [#668](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/668): 1. **Total liquidity** shows **24h and 30d Δ% in the same box** (`+7.782% 24h — 30d`). Operators asked for **24h change only**. 2. **UTC volume** x-axis does **not label every column**. `sparseTimeLabelIndexes(..., maxLabels = 5)` is the intended cap today. Phone Daily (7 bars) already skips columns (e.g. `08-20`, gap, `08-22`…). Same skip on **Hourly** and **Monthly**. Prefer a label on **every** bar; **every second** bar is acceptable if labels would wrap/collide. Bundle into **one** frontend issue. Do **not** split liquidity vs chart — both are `/protocol` Global stats leftovers from the same QA pass. Related: [#667](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/667) **P667-2** (explicitly kept 24h+30d on liquidity), [#668](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/668) **P668** (axis exists; density was never specified), [#652](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/652), [#569](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/569) **P569-1**, [#576](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/576) (trailing tiles ≠ calendar bars), [#653](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/653), [#489](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/489). ## Current codebase Both leftovers are **implemented as specified**, not missing widgets. Tests encode the old product choice, so `make verify-issue-667` / `668` stay green while production still looks wrong. | Layer | Behavior today | |-------|----------------| | **Liquidity tile** | [`ProtocolGlobalStats.tsx`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/components/protocol/ProtocolGlobalStats.tsx) `STATS[0]` passes **two** `deltas`: `liquidity_change_24h_pct` (`protocol-stat-liquidity-24h`, label `24h`) **and** `liquidity_change_30d_pct` (`protocol-stat-liquidity-30d`, label `30d`). One cell, one `.stat-delta-cluster`. Lead copy still says “24h/30d liquidity is vs indexer snapshots.” | | **Volume / fee tiles** | Unchanged and **correct**: Last 24h / 7d / 30d **vol** (and fees) each own one window + one Δ%. Do not strip those 30d tiles. | | **StatBox** | [`StatBox.tsx`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/components/ui/StatBox.tsx) already groups chips with the `$` (`justify-start` / wrap, [#667](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/667)). Comment still says “Prefer `deltas` for liquidity 24h+30d.” | | **Overview JSON** | Additive `liquidity_change_24h_pct` **and** `liquidity_change_30d_pct` stay on `GET /overview` ([`types/index.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/types/index.ts)). Snapshot stock % (**P569-2**). | | **Chart labels** | [`ProtocolVolumeDailyChart.tsx`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/components/protocol/ProtocolVolumeDailyChart.tsx) builds `xLabels` from `sparseTimeLabelIndexes(series.length)` and draws SVG `<text>` under `protocol-volume-chart-xaxis`. Tooltip / `aria-label` already have the full UTC period. | | **Sparse helper** | [`protocolVolumeGrain.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/utils/protocolVolumeGrain.ts) `sparseTimeLabelIndexes(count, maxLabels = 5)`: if `count ≤ 5` label all; else **exactly 5** evenly spaced indexes (first + last inclusive). Comment: “do not label every hourly bar.” Daily min is **7**, monthly min **6**, hourly min **12** — all three grains therefore skip columns on the default phone plot. | | **Axis format** | `formatPeriodAxisLabel`: hourly → `HH` (`14`), daily → `MM-DD` (`08-26`), monthly → `YYYY-MM`. Fixed `viewBox` `320×128`, `PAD_B = 22`, `fontSize="8"`. | | **Tests that lock the leftover** | [`ProtocolPage.test.tsx`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/pages/ProtocolPage.test.tsx) asserts `protocol-stat-liquidity-30d` text. [`e2e/protocol-page.spec.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/e2e/protocol-page.spec.ts) requires the 30d chip visible + grouped (desktop / tablet / phone). [`protocolVolumeGrain.test.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/utils/__tests__/protocolVolumeGrain.test.ts) asserts `sparseTimeLabelIndexes(168).length ≤ 5`. | | **Invariants** | **P569-1**: “USD + inline 24h/30d snapshot Δ%.” **P667-2**: “Liquidity keeps 24h+30d in one `.stat-delta-cluster`.” **P668-1–P668-8**: USD Y-axis + tooltip + grain tabs; **no** x-label density rule. | ## Why this is needed 1. **Two windows in one liquidity box is a retail accounting error.** Volume already has a dedicated **Last 30d vol** tile. Putting `— 30d` next to the 24h chip on **Total liquidity** reads as “this TVL moved 24h *and* 30d,” then the 30d chip is often an em-dash (window not filled). Operators asked for **24h only** in that box. 2. **`#667` shipped the dual-chip on purpose.** **P667-2** / **P569-1** must be **amended**, not treated as a regression. Leaving the 30d chip is not a grouping leftover — it is the wrong product. 3. **Five x-labels is too sparse for the default Daily plot.** Seven UTC days with two unlabeled bars looks broken (`08-20` … `08-22`). Monthly (6–24) and Hourly (12–168) inherit the same `maxLabels = 5` helper, so the skip is not Daily-only. 4. **Tooltip is not a substitute for the axis.** Pointer/keyboard tooltip (**P668-2**) already shows the period. Operators still need to **read the series** without hovering every bar — especially on phone. ## Constraints / guardrails - **Frontend-only.** Do **not** change overview JSON, snapshot SQL, `flow_change_pct`, grain GET allowlist, rollup tables, or wasm. Keep `liquidity_change_30d_pct` on the wire (additive / old clients). Stop **rendering** it on Total liquidity. - **Do not remove Last 30d vol / Last 30d fees.** Those tiles stay. Only the **liquidity** cluster drops 30d. - **Keep `#667` grouping.** Single remaining liquidity chip (`protocol-stat-liquidity-24h`) still sits immediately after the `$` (`justify-start` / wrap). Do **not** restore `justify-between`. - **Keep `#653` chrome.** Flat `StatBox` inside `shell-panel`. No nested `card-glass`. No `PriceChart`. - **Keep `#668` grain contract.** Tabs Hourly / Daily / Monthly (default Daily). `limit` still follows plot width + grain `[min, max]`. Client still rejects non-allowlisted grain/limit **before** fetch. Hide chart on 404/501 (**P652-6**). - **Do not `SUM` `swap_events` or N+1 Llama** to invent extra axis ticks. - **Label density floor.** Daily and Monthly: step **1** (every bar) or step **2** (every other) only. Hourly may use a wider step **only** when a step-2 label would collide in the fixed `viewBox` (168 bars × `MM`/`HH`). Do **not** keep a global `maxLabels = 5`. - **First and last bar stay labeled** when any labels are shown. - **Copy / a11y.** Labels ≤ ~5 words (#489). Keep `TRAILING_LIQUIDITY_24H_TITLE` + `composeStatAriaLabel` on the 24h chip. Tooltip stays text (no `innerHTML` / `eval` / `javascript:`). XSS period / `volume_usd` still render as **text**. - **Δ% semantics unchanged.** Liquidity 24h stays snapshot stock % (**P569-2**). Missing / `null` / non-finite → em-dash. Never `Infinity` / fake `0%`. - **Do not lecture.** No new Global stats banner. Trailing vs UTC-calendar copy stays [#576](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/576). - **Update invariants.** Amend **P569-1** and **P667-2** (24h-only on liquidity). Add an x-axis density rule on **P668** (or a new **P6xx** on the same skill). `docs/frontend.md` § Protocol. New `make verify-issue-*`. Keep `verify-issue-667` / `668` / `652` / `550` / `569` / `586` / `653` / `576` green after those scripts are updated to the new product (do not leave them asserting the 30d liquidity chip or `≤ 5` x-labels). - **No wallet / signing / localStorage** for this census panel. ## Relevant files | Path | Role | |------|------| | [`frontend-dapp/src/components/protocol/ProtocolGlobalStats.tsx`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/components/protocol/ProtocolGlobalStats.tsx) | Drop the 30d liquidity `deltas` entry; keep 24h | | [`frontend-dapp/src/components/ui/StatBox.tsx`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/components/ui/StatBox.tsx) | Comment / `deltas` still valid for one chip; no layout revert | | [`frontend-dapp/src/utils/trailingWindowCopy.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/utils/trailingWindowCopy.ts) | Keep 24h liquidity title; 30d liquidity title unused on this tile | | [`frontend-dapp/src/utils/protocolVolumeGrain.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/utils/protocolVolumeGrain.ts) | Replace `maxLabels = 5` with grain/width-aware step 1 or 2 | | [`frontend-dapp/src/components/protocol/ProtocolVolumeDailyChart.tsx`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/components/protocol/ProtocolVolumeDailyChart.tsx) | Pass grain (and slot/width) into the label picker; keep tooltip | | [`frontend-dapp/src/utils/__tests__/protocolVolumeGrain.test.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/utils/__tests__/protocolVolumeGrain.test.ts) | Daily 7 / monthly 6 label all (or every 2nd); drop `168 ≤ 5` | | [`frontend-dapp/src/pages/ProtocolPage.test.tsx`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/pages/ProtocolPage.test.tsx) | 24h chip present; **no** `protocol-stat-liquidity-30d` in the tile; x-axis text count | | [`frontend-dapp/src/components/ui/__tests__/StatBox.test.tsx`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/components/ui/__tests__/StatBox.test.tsx) | Multi-`deltas` still allowed for other callers; liquidity page no longer uses two | | [`frontend-dapp/e2e/protocol-page.spec.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/e2e/protocol-page.spec.ts) | Stop requiring liquidity-30d; assert 24h-only + x-axis density on Hourly/Daily/Monthly | | [`skills/AGENTS_FRONTEND_PROTOCOL_STATS.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/skills/AGENTS_FRONTEND_PROTOCOL_STATS.md) | Amend P569-1 / P667-2; add P668 x-label density | | [`docs/frontend.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/docs/frontend.md) | Protocol census: liquidity 24h-only; chart labels every bar or every 2nd | | New `scripts/qa/verify-issue-NNN.sh` + `make verify-issue-NNN` | Docs grep + Vitest + Playwright (e2e-smoke @ 5 workers) | ## Recommended direction 1. **Liquidity:** in `ProtocolGlobalStats` `STATS` liquidity `deltas`, keep only the 24h object. Remove `protocol-stat-liquidity-30d` from this tile. Leave `liquidity_change_30d_pct` on `IndexerOverview` unused here. Soften the lead sentence from “24h/30d liquidity” to “24h liquidity is vs indexer snapshots” (still not on-chain genesis). 2. **Do not** invent a second liquidity tile for 30d. Last 30d vol already covers the long window; TVL 30d stays available to API consumers. 3. **X-axis:** replace `sparseTimeLabelIndexes(count, 5)` with a helper that takes `count`, `grain`, and plot/slot width. Default **step 1**. If the formatted label width (`MM-DD` / `HH` / `YYYY-MM` at fontSize 8) exceeds the slot, use **step 2**. Hourly at high `limit` may compute `step = ceil(labelPx / slotPx)` but Daily/Monthly must stay 1 or 2. 4. **Collision:** `textAnchor="middle"`; optional extra `PAD_B` if daily `MM-DD` wraps. Do not rotate the chart into a horizontal USD category plot (**P668-1**). 5. **Tests:** RTL — liquidity tile has 24h chip, **queries 30d chip = 0**. Grain unit tests — 7 daily indexes `[0..6]`; 6 monthly all or every 2nd; hourly 12 all or every 2nd; hourly 168 is width-clamped, **not** hard-capped at 5. Playwright — Hourly / Daily / Monthly: `protocol-volume-chart-xaxis text` count ≥ `ceil(barCount / 2)` and ≤ `barCount`. 6. **Verify script** modeled on `scripts/qa/verify-issue-667.sh` / `668.sh`. Update those scripts’ assertions that still require the 30d liquidity chip or `≤ 5` labels. ## Acceptance criteria - [ ] Total liquidity shows USD + **one** Δ% chip: `protocol-stat-liquidity-24h` (label `24h`). No `30d` text and no `protocol-stat-liquidity-30d` inside `protocol-stat-liquidity`. - [ ] Missing / `null` 24h Δ% → em-dash. Never `Infinity` / fake `0%`. `#667` grouping still holds (chip with `$`, not `justify-between`). - [ ] Last 24h / 7d / 30d **vol** tiles and fee 24h / 7d / 30d tiles unchanged (30d stays on those boxes). - [ ] UTC volume Daily default: every bar labeled, **or** every second bar if a collision test proves step 1 overflows. No unlabeled “orphan” columns while neighbors are labeled at step > 2 on Daily/Monthly. - [ ] Hourly and Monthly use the same density rule (not Daily-only). - [ ] Phone 390 / tablet 820 / desktop 1280, light + dark: labels readable; first + last period visible when the series is non-empty. - [ ] Tooltip + `aria-label` still show full UTC period + USD (unpriced → em-dash). XSS strings stay text. - [ ] Overview keys, grain GET, `#653` chrome, `#576` trailing copy, page order (stats → fees → hub → oracle) unchanged. - [ ] **P569-1** / **P667-2** / **P668** docs + skill amended. `make verify-issue-NNN` green. ## Test plan — all paths | Path | How | |------|-----| | **Liquidity happy** | Fixture with 24h `+7.782` and 30d `12.5`: tile shows `+7.782% 24h` only. `queryByTestId('protocol-stat-liquidity-30d')` is null. | | **Liquidity em-dash** | `liquidity_change_24h_pct` null / missing / non-finite → `— 24h`. 30d key present or absent must not resurrect a 30d chip. | | **Idle TVL $0** | `$0` + null 24h → `$0` + `—`; no `0%` / `Infinity`. | | **Old overview** | Missing `liquidity_change_*` → 24h em-dash; tiles still render (**P652-6**). | | **Volume / fees 30d** | `protocol-stat-volume-30d` / `protocol-stat-fees-30d` still visible with their own Δ%. | | **#667 geometry** | Desktop 1280 / tablet 820 / phone 390: remaining 24h chip `chip.right ≤ tile.right` and not under Last 24h vol. | | **Daily 7 bars (phone)** | `sparse`/`labelIndexes(7, daily)` is all 7 or `[0,2,4,6]`. SVG x-axis text count matches. No two-apart skip like today’s 5-label spread. | | **Daily 90 (wide)** | Step 1 or 2 only. First + last labeled. | | **Hourly 12** | All or every 2nd. | | **Hourly 168** | Step from slot vs label width; **not** forced to 5. Tooltip still works on unlabeled bars. | | **Monthly 6 / 24** | Step 1 or 2. `YYYY-MM` must not overflow into the next label’s box at step 1 — then step 2. | | **Grain switch** | Click Hourly / Daily / Monthly: density rule reapplies; `limit` reset + ResizeObserver debounce unchanged. | | **Empty / all-null / 404** | Empty copy or hide chart; no leftover x-labels. | | **Unpriced bar** | Outlined, not `$0`; axis label still the period. | | **Loading / error** | Skeleton / `RetryError`; no invented ticks. | | **Light / dark** | Axis fill stays `--ink-dim`; bars `--accent`. | | **Playwright** | Extend `e2e/protocol-page.spec.ts`; `--project=e2e-smoke --workers=5`. | ## Test plan — attack, hack, and abuse Display-only leftover. Do **not** re-open fee/volume/TVL math. Fail closed on mis-attribution and injection. | Vector | Expect | |--------|--------| | **XSS in overview / series** | `liquidity_change_24h_pct`, `utc_day` / `utc_hour` / `utc_month`, `volume_usd` with `<script>`, `javascript:`, `<img onerror>` render as **text** or em-dash. No `innerHTML`. No `style=` / `className` from API strings. | | **Axis label injection** | `formatPeriodAxisLabel` slices trusted ISO-like keys. A hostile `utc_day` must not become an SVG `<a href>` or event handler. | | **Wrong-window social engineering** | A leftover `— 30d` on Total liquidity is the abuse (operators read a filled 30d TVL move that is not this tile’s job). RTL/e2e must fail if that chip returns. | | **Sparse-axis social engineering** | Unlabeled bars between labeled dates can be read as “no volume that day.” Density tests are the control. Tooltip must still tell the truth on skip-2 hourly bars. | | **Infinity / `1e309` / NaN** | Em-dash on Δ% and axis USD. Never `Infinity%` or `NaN` ticks. | | **Unpriced treated as $0** | Unpriced stays outlined + tooltip `—`. Do not paint a `$0` bar then a green “flat” story. | | **Query injection** | Grain/limit allowlist unchanged. `from`/`to` still **400**. Extra query junk must not bust the 60s cache (**P668-5**). | | **DoS** | No new GET. No `swap_events` SUM. No Llama N+1. Resize debounce stays. Label helper is O(n) over the already-capped series. | | **Privilege / wallet** | `/protocol` is public census. No connect prompt, no signing, no localStorage of Δ% or grain (grain may stay React state only). | | **Open redirect / ticker** | Unchanged **P550-2**. Axis CSS must not depend on `?ticker=`. | | **Hit-slop** | Long `MM-DD` / `YYYY-MM` labels must not cover the neighbor bar’s focus target. | ## Verification criteria Done when: 1. `make verify-issue-NNN` (new script) is green: skill/docs say liquidity **24h-only** and x-axis step **1 or 2**; Vitest covers both; Playwright e2e-smoke @ 5 workers checks the 30d liquidity chip is gone and Hourly/Daily/Monthly x-label counts meet the floor. 2. `make verify-issue-667` `668` `652` `550` `569` `586` `653` `576` stay green **after** their assertions are updated to the new product (no leftover “must show liquidity-30d” / “≤ 5 x-labels”). 3. Manual `/protocol` at 390 / 820 / 1280, light + dark: Total liquidity has one 24h chip; Daily/Hourly/Monthly show a label on every bar or every second bar. 4. No overview / indexer / wasm diff required for merge. ## Out of scope - Changing snapshot vs flow Δ% math, grain GET, or hub/oracle cards. - Adding a 30d liquidity tile elsewhere. - Flattening chrome again (#653). - Unique-trader headline / lecture banner (#489 / **P550**). - DeFiLlama range dump (#631 / **P652-3**).
PlasticDigits commented 2026-08-27 00:17:42 +00:00 (Migrated from gitlab.com)

marked as related to #667

marked as related to #667
PlasticDigits commented 2026-08-27 00:17:42 +00:00 (Migrated from gitlab.com)

marked as related to #668

marked as related to #668
PlasticDigits commented 2026-08-27 00:17:43 +00:00 (Migrated from gitlab.com)

marked as related to #652

marked as related to #652
PlasticDigits commented 2026-08-27 00:17:44 +00:00 (Migrated from gitlab.com)

marked as related to #569

marked as related to #569
PlasticDigits commented 2026-08-27 00:35:38 +00:00 (Migrated from gitlab.com)

mentioned in merge request !1181

mentioned in merge request !1181
PlasticDigits commented 2026-08-27 00:37:23 +00:00 (Migrated from gitlab.com)

Implemented on issue-677-protocol-leftovers — !1181.

Done

  • Total liquidity is 24h-only (protocol-stat-liquidity-24h). No protocol-stat-liquidity-30d on the tile. Volume / fee 30d tiles unchanged.
  • #667 grouping kept (justify-start / wrap). Em-dash on missing 24h Δ%.
  • UTC volume x-axis: Daily/Monthly step 1 or 2; Hourly may widen when step 2 collides. First + last labeled. No maxLabels = 5.
  • P569-1 / P667-2 / P668-9 amended in skills/AGENTS_FRONTEND_PROTOCOL_STATS.md + docs/frontend.md. make verify-issue-677 green (Vitest + Playwright e2e-smoke @ 5 workers).

Not done (out of scope or env)

  • No overview / indexer / wasm change (as specified).
  • No second 30d liquidity tile.
  • Manual 390 / 820 / 1280 light+dark walkthrough (Playwright covered those viewports).
  • Related indexer integration volume_aggregator_refreshes_daily_and_change already fails on this VM; not in this diff.
Implemented on `issue-677-protocol-leftovers` — !1181. **Done** - Total liquidity is 24h-only (`protocol-stat-liquidity-24h`). No `protocol-stat-liquidity-30d` on the tile. Volume / fee 30d tiles unchanged. - `#667` grouping kept (`justify-start` / wrap). Em-dash on missing 24h Δ%. - UTC volume x-axis: Daily/Monthly step 1 or 2; Hourly may widen when step 2 collides. First + last labeled. No `maxLabels = 5`. - **P569-1** / **P667-2** / **P668-9** amended in `skills/AGENTS_FRONTEND_PROTOCOL_STATS.md` + `docs/frontend.md`. `make verify-issue-677` green (Vitest + Playwright e2e-smoke @ 5 workers). **Not done (out of scope or env)** - No overview / indexer / wasm change (as specified). - No second 30d liquidity tile. - Manual 390 / 820 / 1280 light+dark walkthrough (Playwright covered those viewports). - Related indexer integration `volume_aggregator_refreshes_daily_and_change` already fails on this VM; not in this diff.
PlasticDigits commented 2026-08-27 04:45:23 +00:00 (Migrated from gitlab.com)

mentioned in commit b7b6f4e9ca

mentioned in commit b7b6f4e9caef31ac6ca801faa6ae5fa1d9d6ec8b
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-08-27 04:45:23 +00:00
PlasticDigits commented 2026-08-27 05:29:51 +00:00 (Migrated from gitlab.com)

mentioned in issue #686

mentioned in issue #686
PlasticDigits commented 2026-08-27 05:29:53 +00:00 (Migrated from gitlab.com)

marked as related to #686

marked as related to #686
PlasticDigits commented 2026-08-27 05:30:24 +00:00 (Migrated from gitlab.com)

Merged via !1181 (b7b6f4e9) onto main 36d64528.

make verify-issue-677 PASS (7/7), including Playwright e2e/protocol-page.spec.ts at 5 workers (PLAYWRIGHT_SKIP_CHAIN=1). Coolify leftover: frontend rebuild so liquidity is 24h-only Δ% and the UTC volume axis is denser. Do not reopen unless P569-1 / P668-9 is wrong. Post-merge ops: #686.

Merged via !1181 (`b7b6f4e9`) onto `main` `36d64528`. `make verify-issue-677` **PASS** (7/7), including Playwright `e2e/protocol-page.spec.ts` at 5 workers (`PLAYWRIGHT_SKIP_CHAIN=1`). Coolify leftover: frontend rebuild so liquidity is 24h-only Δ% and the UTC volume axis is denser. Do not reopen unless P569-1 / P668-9 is wrong. Post-merge ops: #686.
PlasticDigits commented 2026-08-27 09:00:10 +00:00 (Migrated from gitlab.com)

mentioned in issue #689

mentioned in issue #689
PlasticDigits commented 2026-08-27 09:00:14 +00:00 (Migrated from gitlab.com)

marked as related to #689

marked as related to #689
PlasticDigits commented 2026-08-29 08:04:46 +00:00 (Migrated from gitlab.com)

mentioned in issue #703

mentioned in issue #703
PlasticDigits commented 2026-08-29 08:04:47 +00:00 (Migrated from gitlab.com)

marked as related to #703

marked as related to #703
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
code/cl8y-dex-terraclassic#677
No description provided.