fix: /protocol Monthly UTC chart — phone x-axis overlap (last 12 months + YY-MM) #703

Closed
opened 2026-08-29 08:04:45 +00:00 by PlasticDigits · 9 comments
PlasticDigits commented 2026-08-29 08:04:45 +00:00 (Migrated from gitlab.com)

Summary

On phone, /protocol UTC chart Monthly x-axis labels overlap into an unreadable pile (2024-09 … 2026-08 stacked on the same baseline). The plot still fetches the full 24 UTC calendar months because bar-slot math (~12px) ignores that monthly labels are YYYY-MM (~7 glyphs). #677 P668-9 then forces Daily/Monthly to label every bar or every second bar — so ~12–24 × 2026-08 strings collide on a ~260–300px plot.

Bundle into one frontend issue: (1) request the last 12 UTC calendar months on phone-width (drop to 6 if 12 still collides), keep 24 on desktop; (2) shorten the axis to a 2-digit year (26-08); (3) keep labels horizontal — do not rotate/skew SVG text. Tooltip / bar aria-label keep full YYYY-MM. Same plot serves Volume / Liquidity / Fees (#689) — one series, exclusive metric.

Do not split “fewer months” vs “shorter labels” vs “no rotate” into separate issues. Do not reopen closed #668 / #677 / #689. Do not change indexer monthly retain ≥ 24 months or GET limit max 24.

Related: #668 (grain + width clamp), #677 (dense x-axis), #689 (metric toggle), #652 (UTC series), #576 (trailing tiles ≠ calendar bars), #489 (short copy / no extra cognitive work), #653 (one chrome layer).

Current codebase

The overlap is specified, not a missing widget. make verify-issue-677 / 668 / 689 stay green while phone Monthly is illegible.

Layer Behavior today
Chart ProtocolVolumeDailyChart.tsx — ResizeObserver sets limit via limitFromPlotWidth(width − PAD_L − PAD_R, grain) (~12px/bar). SVG text under protocol-volume-chart-xaxis is textAnchor="middle" fontSize="8", no transform. Tooltip + bar aria-label already include the full UTC period (pointPeriod).
Width → limit protocolVolumeGrain.ts PROTOCOL_VOLUME_BAR_SLOT_PX = 12, monthly clamp 6–24. Phone plot ~260–300px → floor(260/12)=21 … floor(320/12)=26 → 24 months on 390px. Desktop 1280 also 24 (max). Tests lock limitFromPlotWidth(1280, 'monthly') === 24.
X-axis step (P668-9) timeLabelStep: Daily/Monthly only 1 or 2 even when estimatedAxisLabelWidthPx('monthly') (7 glyphs × 4.8 + 2 ≈ 36px) needs step 3–4. Hourly may widen. timeLabelIndexes always labels first and last. Adjacent last-two labels (step then push(last)) can sit one slot apart (~11px vs ~36px glyph run).
Axis format formatPeriodAxisLabel('2026-08', 'monthly') returns 2026-08. Hourly is HH, daily MM-DD (5 glyphs). Tests assert monthly stays 2026-08.
Fetch useProtocolVolumeSeriesQuery.ts query key (metric, grain, limit). client.ts getProtocolVolumeSeries / getProtocolLiquiditySeries / getProtocolFeesSeries reject non-allowlisted grain/limit before fetch.
Indexer GET /api/v1/protocol/{volume,liquidity,fees}/daily?grain=monthly&limit= max 24. Newest-last. Monthly = UTC calendar month (YYYY-MM), not trailing 30d. Rollup retain ≥ 24 months (P668-7 / P689-6). No GET-path SUM.
E2E e2e/protocol-page.spec.ts assertVolumeXAxisDensity at 1280 requires monthly labelCount ≥ ceil(barCount/2). Phone 390 tests never click Monthly or assert non-overlapping <text> boxes. QA 10.2.18 is desktop-density, not collision.
Invariants P668-4 monthly 6–24 from 12px slots. P668-9 Monthly step 1 or 2. P668-1 bars stay vertical time series (do not rotate into a horizontal-bar USD chart — that is the plot, not the tick text; still: do not rotate tick text either).

Collision math (phone): 24 × YYYY-MM at step 2 ≈ 13 labels × ~36px on a ~260px plot. Operators see 2024-09 through 2026-08 overprinted; the current month bar is readable, the axis is not.

Why this is needed

  1. Phone Monthly is unusable. Operators cannot tell which bar is which month without hovering every column. Tooltip (P668-2) is not a substitute for a readable axis on a census chart.
  2. #677 densified labels for Daily/Hourly; Monthly inherited the same step-1-or-2 rule with 7-glyph keys. That is correct for 6 short bars and wrong for 24 × YYYY-MM on 390px.
  3. Rotated / vertical tick text is rejected. Reading 2026-08 sideways is extra cognitive work (#489). Horizontal shorter strings + fewer months is the product choice.
  4. Trailing Last 24h / 7d / 30d tiles stay as-is (#576). This is only the UTC calendar-month series. Do not retitle Monthly as “last 12 months trailing.”
  5. Young / --fresh indexers already return idle zeros / available months (P668-7). Showing 6–12 newest months on phone does not require GET-path backfill.

Constraints / guardrails

  • Frontend-only for the overlap. Do not lower indexer monthly retain below 24 months. Do not lower GET allowlist max below 24. Desktop may still request 24.
  • Newest-last. “Last 12 months” = current limit (most recent N UTC calendar months), not a new from/to (those stay 400).
  • Do not rotate SVG x-labels (transform="rotate(…)", CSS writing-mode, textPath). Horizontal only.
  • Do not restore 7d / 30d grain, PriceChart, Llama GET /defillama/daily, nested card-glass, or overlay Volume+Liquidity+Fees.
  • Do not weaken Daily/Hourly P668-9 (every bar or every 2nd; hourly may widen). Do not restore global maxLabels = 5.
  • Axis vs tooltip: axis may be YY-MM; tooltip + aria-label keep YYYY-MM (and USD / em-dash). Century wrap (99 vs 00) is OK on the axis; tooltip disambiguates.
  • XSS: period / usd fields stay text (no innerHTML / eval / javascript:). Malicious utc_month must not break layout into executable markup.
  • Client allowlist before fetch stays (P668-4 / client tests). Phone cap must still be an integer in [6, 24].
  • Metric toggle (#689): switching Volume / Liquidity / Fees keeps grain and the same monthly limit. Empty / 404 sibling routes unchanged.
  • Copy ≤ ~5 words on visible grain subtitle (UTC calendar month stays). No lecture about “showing last 12 months.”
  • Amend P668-4 / P668-9 in skills/AGENTS_FRONTEND_PROTOCOL_STATS.md — do not treat today’s 24-on-phone as a regression to keep.

Relevant files

  • frontend-dapp/src/utils/protocolVolumeGrain.ts — limitFromPlotWidth, estimatedAxisLabelWidthPx, timeLabelStep, formatPeriodAxisLabel
  • frontend-dapp/src/utils/__tests__/protocolVolumeGrain.test.ts
  • frontend-dapp/src/components/protocol/ProtocolVolumeDailyChart.tsx
  • frontend-dapp/src/components/protocol/useProtocolVolumeSeriesQuery.ts (query key already (grain, limit))
  • frontend-dapp/src/services/indexer/client.ts — allowlisted grain + limit
  • frontend-dapp/src/pages/ProtocolPage.test.tsx
  • frontend-dapp/e2e/protocol-page.spec.ts — add phone 390 Monthly collision + bar-count asserts
  • skills/AGENTS_FRONTEND_PROTOCOL_STATS.md — P668-4 / P668-9 + this issue’s IDs
  • docs/frontend.md, docs/testing.md, QA_TEMPLATE.md (extend 10.2.18 or add a Monthly-phone row)
  • scripts/qa/verify-issue-*.sh + Makefile verify-issue-* for this IID
  • Indexer: no schema/GET change unless a test proves the client requested an illegal limit (then fix the client)

Phone-first months + shorter year. No rotated ticks.

  1. Monthly limit on narrow plots: For grain === 'monthly', do not use the 12px bar slot as the only cap. On plot widths used at 390 (and tablet 820 if labels still collide), clamp monthly limit to 12 (preferred) or 6 (PROTOCOL_VOLUME_GRAIN_MIN.monthly). Desktop 1280 stays 24. limitFromPlotWidth(320, 'monthly') must be ≤ 12, not 24.
  2. Axis label YY-MM: formatPeriodAxisLabel('2026-08', 'monthly') → 26-08 (same glyph budget as daily MM-DD). Update estimatedAxisLabelWidthPx('monthly') to 5 glyphs. Tooltip / aria-label / pointPeriod stay 2026-08.
  3. Keep P668-9 density on the bars that remain. With 6–12 months, step 1 or 2 should fit without overlap. First and last month stay labeled. If 12 × YY-MM still collides on 390, drop to 6 bars rather than widening monthly step past 2 or rotating text.
  4. Collision test: Playwright at 390×844, Monthly, measure protocol-volume-chart-xaxis text bounding boxes — no pair overlaps (small epsilon). Assert barCount ≤ 12 (or 6). Repeat Volume / Liquidity / Fees. Light + dark not required if labels are SVG fill=var(--ink-dim).
  5. Docs: skill + docs/frontend.md + QA row: phone Monthly shows last 6–12 UTC months, axis YY-MM, horizontal. make verify-issue-677 / 668 / 689 stay green (amend asserts that lock 24-on-phone / 2026-08 on the axis).

Acceptance criteria

  • M1 Phone 390×844 Monthly: x-axis labels are horizontal, do not overlap, and are readable without tilting the device. No rotate / writing-mode on tick text.
  • M2 Phone Monthly requests ≤ 12 (and ≥ 6) UTC calendar months — the newest months (newest-last). Desktop 1280 Monthly may still show up to 24. Tablet 820: no overlap (12 or 24, implementer’s call if 24 fits).
  • M3 Monthly axis uses 2-digit year (YY-MM, e.g. 26-08). Tooltip + bar aria-label still include full YYYY-MM + USD (unpriced → em-dash).
  • M4 Hourly / Daily unchanged: P668-9 density, daily MM-DD, hourly HH. Trailing Last 24h/7d/30d tiles unchanged. Grain default Daily. Metric default Volume.
  • M5 Volume / Liquidity / Fees share the same monthly limit and axis rules. Switching metric does not refetch a different month count. 404/501 volume still hides the chart; sibling 404 hides that metric only.
  • M6 Client still reject-lists grain/limit before fetch. Indexer GET max monthly 24, from/to 400, 60s cache, rollup-only — unchanged.
  • M7 XSS period / usd still text. No PriceChart, Llama, nested card-glass, 7d/30d grain.
  • M8 Skill + docs + QA_TEMPLATE + make verify-issue-{this} + Playwright phone Monthly. Keep verify-issue-677 / 668 / 689 / 652 / 667 / 653 green.

Test plan (all paths)

Path Steps Expected
Phone Monthly Volume 390×844 /protocol → Monthly ≤12 bars; YY-MM ticks; no bbox overlap; subtitle UTC calendar month; first+last labeled
Phone Monthly Liquidity / Fees Same viewport → Liquidity, then Fees Same month count; stock/flow semantics unchanged (P689-3); axis still readable
Phone Daily / Hourly 390 → Daily, Hourly No regression vs P668-9; not forced to 6–12 the way Monthly is
Tablet 820 Monthly 820×1180 → Monthly No overlap; 12 or 24 OK if labels fit
Desktop 1280 Monthly 1280 → Monthly Up to 24 months; labels readable (step 1 or 2 + YY-MM); fills panel (P668-9 viewBox)
Resize 1280 Monthly → resize to 390 Debounced limit drop; query key updates; no stale 24-label pile
Empty / idle --fresh / all "0" Idle bars + readable ticks; not empty-state unless series empty / all null
Unpriced volume_usd/fees_usd null; liquidity missing sample Outlined bar; tooltip em-dash; axis still YY-MM
Old indexer Volume 404 Chart hidden. Liquidity/fees 404: that metric empty, Volume still works
Grain / metric switch Daily → Monthly → Hourly; Volume → Fees Tooltip reset on grain; metric keeps grain; no overlay
RTL unit protocolVolumeGrain.test.ts Phone width monthly ≤12; formatPeriodAxisLabel monthly YY-MM; 2026-08 still in tooltip tests
Vitest ProtocolPage Monthly 6-bar fixture Axis contains 26-01 style, not overlapping 24 × 2026-
Light/dark 390 Monthly Ticks use theme ink-dim; still horizontal

Playwright 5 workers on e2e-smoke. No wasm / LocalTerra required.

Test plan (attack, hack, abuse)

Vector Attack Expected
Limit injection UI/state forces limit=999, 1.5, 0, -1, monthly'; DROP Client throws before fetch; indexer 400 if it ever hits the wire. Phone cap cannot punch a hole in the allowlist.
Grain injection grain=week, grain=daily OR 1=1, grain=monthly; Rejected client + 400. No extra query junk cache bust (P668-5).
from/to / window Client or proxy adds from/to/days/metric on grain GET Still 400. Do not add a new range API to “select last 12 months.”
XSS utc_month "<script>…</script>", javascript:, "><img onerror> SVG <text> + tooltip text only. querySelector('script') null. Layout may be ugly; must not execute.
USD / Infinity volume_usd Infinity / NaN / huge NUMERIC Y-axis P668-1 ($0 / finite ticks). No Infinity on axis.
DoS Rapid grain/metric/resize spam Debounce 160ms; 60s query cache; one series fetch; no Llama N+1; no GET-path SUM.
Confused deputy / methodology Treat phone “12 months” as trailing 365d or Llama daily Still UTC calendar months from rollup tables. Trailing tiles unchanged.
Clickjack / overlay Extra card-glass around plot Forbidden (C653). Chart stays inside protocol-global-stats.
Open redirect / URL Axis labels as <a href> Ticks are not links. Explorer stays on address rows only.
Century / malformed period utc_month 26-08, 2026-13, empty Format helper fail-closed to a short text fallback; no crash; tooltip shows raw period as text.
Privacy N/A Series is global protocol census, not wallet-identifying.

Verification criteria

  • make verify-issue-{this} all steps PASS (docs grep + Vitest grain helper + ProtocolPage + Playwright phone Monthly collision + related 677/668/689).
  • make verify-issue-677 / make verify-issue-668 / make verify-issue-689 stay green after asserts that required 24 months on phone or axis 2026-08 are updated.
  • Manual: Chrome 390×844 /protocol → Monthly, Volume then Liquidity / Fees — ticks readable, no head-tilt, last 6 or 12 months not a 2024–2026 smear. Desktop Monthly still shows longer history when width allows.

Close when AC M1–M8 are met. Do not close on desktop-only screenshots.

## Summary On phone, `/protocol` **UTC** chart **Monthly** x-axis labels overlap into an unreadable pile (`2024-09` … `2026-08` stacked on the same baseline). The plot still fetches the full **24** UTC calendar months because bar-slot math (`~12px`) ignores that monthly labels are `YYYY-MM` (~7 glyphs). **[#677](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/677)** **P668-9** then forces Daily/Monthly to label every bar or every second bar — so ~12–24 × `2026-08` strings collide on a ~260–300px plot. Bundle into **one** frontend issue: **(1)** request the **last 12 UTC calendar months** on phone-width (drop to **6** if 12 still collides), keep **24** on desktop; **(2)** shorten the **axis** to a 2-digit year (`26-08`); **(3)** keep labels **horizontal** — do **not** rotate/skew SVG text. Tooltip / bar `aria-label` keep full `YYYY-MM`. Same plot serves Volume / Liquidity / Fees ([#689](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/689)) — one series, exclusive metric. Do **not** split “fewer months” vs “shorter labels” vs “no rotate” into separate issues. Do **not** reopen closed [#668](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/668) / [#677](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/677) / [#689](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/689). Do **not** change indexer monthly retain ≥ 24 months or GET `limit` max 24. Related: [#668](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/668) (grain + width clamp), [#677](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/677) (dense x-axis), [#689](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/689) (metric toggle), [#652](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/652) (UTC series), [#576](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/576) (trailing tiles ≠ calendar bars), [#489](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/489) (short copy / no extra cognitive work), [#653](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/653) (one chrome layer). ## Current codebase The overlap is **specified**, not a missing widget. `make verify-issue-677` / `668` / `689` stay green while phone Monthly is illegible. | Layer | Behavior today | |-------|----------------| | **Chart** | [`ProtocolVolumeDailyChart.tsx`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/components/protocol/ProtocolVolumeDailyChart.tsx) — `ResizeObserver` sets `limit` via `limitFromPlotWidth(width − PAD_L − PAD_R, grain)` (~12px/bar). SVG `text` under `protocol-volume-chart-xaxis` is `textAnchor="middle"` `fontSize="8"`, **no** `transform`. Tooltip + bar `aria-label` already include the full UTC period (`pointPeriod`). | | **Width → limit** | [`protocolVolumeGrain.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/utils/protocolVolumeGrain.ts) `PROTOCOL_VOLUME_BAR_SLOT_PX = 12`, monthly clamp **6–24**. Phone plot ~260–300px → `floor(260/12)=21` … `floor(320/12)=26` → **24 months** on 390px. Desktop 1280 also 24 (max). Tests lock `limitFromPlotWidth(1280, 'monthly') === 24`. | | **X-axis step (P668-9)** | `timeLabelStep`: Daily/Monthly **only** 1 or 2 even when `estimatedAxisLabelWidthPx('monthly')` (7 glyphs × 4.8 + 2 ≈ **36px**) needs step 3–4. Hourly may widen. `timeLabelIndexes` always labels **first and last**. Adjacent last-two labels (step then `push(last)`) can sit one slot apart (~11px vs ~36px glyph run). | | **Axis format** | `formatPeriodAxisLabel('2026-08', 'monthly')` returns **`2026-08`**. Hourly is `HH`, daily `MM-DD` (5 glyphs). Tests assert monthly stays `2026-08`. | | **Fetch** | [`useProtocolVolumeSeriesQuery.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/components/protocol/useProtocolUtcSeriesQuery.ts) query key `(metric, grain, limit)`. [`client.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/src/services/indexer/client.ts) `getProtocolVolumeSeries` / `getProtocolLiquiditySeries` / `getProtocolFeesSeries` reject non-allowlisted grain/limit **before** fetch. | | **Indexer** | `GET /api/v1/protocol/{volume,liquidity,fees}/daily?grain=monthly&limit=` max **24**. Newest-last. Monthly = UTC calendar month (`YYYY-MM`), not trailing 30d. Rollup retain ≥ 24 months (**P668-7** / **P689-6**). No GET-path SUM. | | **E2E** | [`e2e/protocol-page.spec.ts`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/frontend-dapp/e2e/protocol-page.spec.ts) `assertVolumeXAxisDensity` at **1280** requires monthly `labelCount ≥ ceil(barCount/2)`. Phone 390 tests never click Monthly or assert non-overlapping `<text>` boxes. QA 10.2.18 is desktop-density, not collision. | | **Invariants** | **P668-4** monthly 6–24 from 12px slots. **P668-9** Monthly step 1 or 2. **P668-1** bars stay vertical time series (do not rotate into a horizontal-bar USD chart — that is the **plot**, not the tick text; still: do not rotate tick text either). | **Collision math (phone):** 24 × `YYYY-MM` at step 2 ≈ 13 labels × ~36px on a ~260px plot. Operators see `2024-09` through `2026-08` overprinted; the current month bar is readable, the axis is not. ## Why this is needed 1. **Phone Monthly is unusable.** Operators cannot tell which bar is which month without hovering every column. Tooltip (**P668-2**) is not a substitute for a readable axis on a census chart. 2. **[#677](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/677) densified labels for Daily/Hourly; Monthly inherited the same step-1-or-2 rule with 7-glyph keys.** That is correct for 6 short bars and wrong for 24 × `YYYY-MM` on 390px. 3. **Rotated / vertical tick text is rejected.** Reading `2026-08` sideways is extra cognitive work ([#489](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/489)). Horizontal shorter strings + fewer months is the product choice. 4. **Trailing Last 24h / 7d / 30d tiles stay as-is** ([#576](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/576)). This is only the UTC calendar-month series. Do not retitle Monthly as “last 12 months trailing.” 5. **Young / `--fresh` indexers** already return idle zeros / available months (**P668-7**). Showing 6–12 newest months on phone does not require GET-path backfill. ## Constraints / guardrails - **Frontend-only** for the overlap. Do **not** lower indexer monthly retain below 24 months. Do **not** lower GET allowlist max below 24. Desktop may still request 24. - **Newest-last.** “Last 12 months” = current `limit` (most recent N UTC calendar months), not a new `from`/`to` (those stay **400**). - **Do not rotate** SVG x-labels (`transform="rotate(…)"`, CSS `writing-mode`, `textPath`). Horizontal only. - **Do not** restore `7d` / `30d` grain, `PriceChart`, Llama `GET /defillama/daily`, nested `card-glass`, or overlay Volume+Liquidity+Fees. - **Do not** weaken Daily/Hourly **P668-9** (every bar or every 2nd; hourly may widen). Do **not** restore global `maxLabels = 5`. - **Axis vs tooltip:** axis may be `YY-MM`; tooltip + `aria-label` keep `YYYY-MM` (and USD / em-dash). Century wrap (`99` vs `00`) is OK on the axis; tooltip disambiguates. - **XSS:** period / usd fields stay **text** (no `innerHTML` / `eval` / `javascript:`). Malicious `utc_month` must not break layout into executable markup. - **Client allowlist before fetch** stays (**P668-4** / client tests). Phone cap must still be an integer in `[6, 24]`. - **Metric toggle** ([#689](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/issues/689)): switching Volume / Liquidity / Fees **keeps** grain and the same monthly `limit`. Empty / 404 sibling routes unchanged. - **Copy** ≤ ~5 words on visible grain subtitle (`UTC calendar month` stays). No lecture about “showing last 12 months.” - Amend **P668-4** / **P668-9** in [`skills/AGENTS_FRONTEND_PROTOCOL_STATS.md`](https://gitlab.com/PlasticDigits/cl8y-dex-terraclassic/-/blob/main/skills/AGENTS_FRONTEND_PROTOCOL_STATS.md) — do not treat today’s 24-on-phone as a regression to keep. ## Relevant files - `frontend-dapp/src/utils/protocolVolumeGrain.ts` — `limitFromPlotWidth`, `estimatedAxisLabelWidthPx`, `timeLabelStep`, `formatPeriodAxisLabel` - `frontend-dapp/src/utils/__tests__/protocolVolumeGrain.test.ts` - `frontend-dapp/src/components/protocol/ProtocolVolumeDailyChart.tsx` - `frontend-dapp/src/components/protocol/useProtocolVolumeSeriesQuery.ts` (query key already `(grain, limit)`) - `frontend-dapp/src/services/indexer/client.ts` — allowlisted `grain` + `limit` - `frontend-dapp/src/pages/ProtocolPage.test.tsx` - `frontend-dapp/e2e/protocol-page.spec.ts` — add **phone 390 Monthly** collision + bar-count asserts - `skills/AGENTS_FRONTEND_PROTOCOL_STATS.md` — **P668-4** / **P668-9** + this issue’s IDs - `docs/frontend.md`, `docs/testing.md`, `QA_TEMPLATE.md` (extend 10.2.18 or add a Monthly-phone row) - `scripts/qa/verify-issue-*.sh` + `Makefile` `verify-issue-*` for this IID - Indexer: **no** schema/GET change unless a test proves the client requested an illegal `limit` (then fix the client) ## Recommended direction **Phone-first months + shorter year. No rotated ticks.** 1. **Monthly `limit` on narrow plots:** For `grain === 'monthly'`, do not use the 12px bar slot as the only cap. On plot widths used at **390** (and tablet 820 if labels still collide), clamp monthly `limit` to **12** (preferred) or **6** (`PROTOCOL_VOLUME_GRAIN_MIN.monthly`). Desktop **1280** stays **24**. `limitFromPlotWidth(320, 'monthly')` must be **≤ 12**, not 24. 2. **Axis label `YY-MM`:** `formatPeriodAxisLabel('2026-08', 'monthly')` → `26-08` (same glyph budget as daily `MM-DD`). Update `estimatedAxisLabelWidthPx('monthly')` to 5 glyphs. Tooltip / `aria-label` / `pointPeriod` stay `2026-08`. 3. **Keep P668-9 density on the bars that remain.** With 6–12 months, step 1 or 2 should fit without overlap. First and last month stay labeled. If 12 × `YY-MM` still collides on 390, **drop to 6 bars** rather than widening monthly step past 2 or rotating text. 4. **Collision test:** Playwright at 390×844, Monthly, measure `protocol-volume-chart-xaxis text` bounding boxes — no pair overlaps (small epsilon). Assert `barCount ≤ 12` (or 6). Repeat Volume / Liquidity / Fees. Light + dark not required if labels are SVG `fill=var(--ink-dim)`. 5. **Docs:** skill + `docs/frontend.md` + QA row: phone Monthly shows last 6–12 UTC months, axis `YY-MM`, horizontal. `make verify-issue-677` / `668` / `689` stay green (amend asserts that lock 24-on-phone / `2026-08` on the axis). ## Acceptance criteria - [ ] **M1** Phone 390×844 Monthly: x-axis labels are horizontal, **do not overlap**, and are readable without tilting the device. No `rotate` / `writing-mode` on tick text. - [ ] **M2** Phone Monthly requests **≤ 12** (and ≥ 6) UTC calendar months — the **newest** months (newest-last). Desktop 1280 Monthly may still show up to **24**. Tablet 820: no overlap (12 or 24, implementer’s call if 24 fits). - [ ] **M3** Monthly **axis** uses 2-digit year (`YY-MM`, e.g. `26-08`). Tooltip + bar `aria-label` still include full `YYYY-MM` + USD (unpriced → em-dash). - [ ] **M4** Hourly / Daily unchanged: **P668-9** density, daily `MM-DD`, hourly `HH`. Trailing Last 24h/7d/30d tiles unchanged. Grain default Daily. Metric default Volume. - [ ] **M5** Volume / Liquidity / Fees share the same monthly `limit` and axis rules. Switching metric does not refetch a different month count. 404/501 volume still hides the chart; sibling 404 hides that metric only. - [ ] **M6** Client still reject-lists grain/limit before fetch. Indexer GET max monthly 24, `from`/`to` **400**, 60s cache, rollup-only — **unchanged**. - [ ] **M7** XSS period / usd still **text**. No `PriceChart`, Llama, nested `card-glass`, `7d`/`30d` grain. - [ ] **M8** Skill + docs + `QA_TEMPLATE` + `make verify-issue-{this}` + Playwright phone Monthly. Keep `verify-issue-677` / `668` / `689` / `652` / `667` / `653` green. ## Test plan (all paths) | Path | Steps | Expected | |------|--------|----------| | Phone Monthly Volume | 390×844 `/protocol` → Monthly | ≤12 bars; `YY-MM` ticks; no bbox overlap; subtitle `UTC calendar month`; first+last labeled | | Phone Monthly Liquidity / Fees | Same viewport → Liquidity, then Fees | Same month count; stock/flow semantics unchanged (**P689-3**); axis still readable | | Phone Daily / Hourly | 390 → Daily, Hourly | No regression vs **P668-9**; not forced to 6–12 the way Monthly is | | Tablet 820 Monthly | 820×1180 → Monthly | No overlap; 12 or 24 OK if labels fit | | Desktop 1280 Monthly | 1280 → Monthly | Up to 24 months; labels readable (step 1 or 2 + `YY-MM`); fills panel (**P668-9** viewBox) | | Resize | 1280 Monthly → resize to 390 | Debounced `limit` drop; query key updates; no stale 24-label pile | | Empty / idle | `--fresh` / all `"0"` | Idle bars + readable ticks; not empty-state unless series empty / all `null` | | Unpriced | `volume_usd`/`fees_usd` null; liquidity missing sample | Outlined bar; tooltip em-dash; axis still `YY-MM` | | Old indexer | Volume 404 | Chart hidden. Liquidity/fees 404: that metric empty, Volume still works | | Grain / metric switch | Daily → Monthly → Hourly; Volume → Fees | Tooltip reset on grain; metric keeps grain; no overlay | | RTL unit | `protocolVolumeGrain.test.ts` | Phone width monthly ≤12; `formatPeriodAxisLabel` monthly `YY-MM`; `2026-08` still in tooltip tests | | Vitest ProtocolPage | Monthly 6-bar fixture | Axis contains `26-01` style, not overlapping 24 × `2026-` | | Light/dark | 390 Monthly | Ticks use theme `ink-dim`; still horizontal | Playwright **5 workers** on `e2e-smoke`. No wasm / LocalTerra required. ## Test plan (attack, hack, abuse) | Vector | Attack | Expected | |--------|--------|----------| | **Limit injection** | UI/state forces `limit=999`, `1.5`, `0`, `-1`, `monthly'; DROP` | Client throws before fetch; indexer **400** if it ever hits the wire. Phone cap cannot punch a hole in the allowlist. | | **Grain injection** | `grain=week`, `grain=daily OR 1=1`, `grain=monthly;` | Rejected client + **400**. No extra query junk cache bust (**P668-5**). | | **from/to / window** | Client or proxy adds `from`/`to`/`days`/`metric` on grain GET | Still **400**. Do not add a new range API to “select last 12 months.” | | **XSS utc_month** | `"<script>…</script>"`, `javascript:`, `"><img onerror>` | SVG `<text>` + tooltip **text only**. `querySelector('script')` null. Layout may be ugly; must not execute. | | **USD / Infinity** | `volume_usd` `Infinity` / `NaN` / huge NUMERIC | Y-axis **P668-1** (`$0` / finite ticks). No `Infinity` on axis. | | **DoS** | Rapid grain/metric/resize spam | Debounce 160ms; 60s query cache; one series fetch; no Llama N+1; no GET-path `SUM`. | | **Confused deputy / methodology** | Treat phone “12 months” as trailing 365d or Llama daily | Still UTC calendar months from rollup tables. Trailing tiles unchanged. | | **Clickjack / overlay** | Extra `card-glass` around plot | Forbidden (**C653**). Chart stays inside `protocol-global-stats`. | | **Open redirect / URL** | Axis labels as `<a href>` | Ticks are not links. Explorer stays on address rows only. | | **Century / malformed period** | `utc_month` `26-08`, `2026-13`, empty | Format helper fail-closed to a short text fallback; no crash; tooltip shows raw period as text. | | **Privacy** | N/A | Series is global protocol census, not wallet-identifying. | ## Verification criteria - `make verify-issue-{this}` **all steps PASS** (docs grep + Vitest grain helper + ProtocolPage + Playwright phone Monthly collision + related 677/668/689). - `make verify-issue-677` / `make verify-issue-668` / `make verify-issue-689` stay green after asserts that **required 24 months on phone** or **axis `2026-08`** are updated. - Manual: Chrome 390×844 `/protocol` → Monthly, Volume then Liquidity / Fees — ticks readable, no head-tilt, last 6 or 12 months not a 2024–2026 smear. Desktop Monthly still shows longer history when width allows. Close when AC **M1–M8** are met. Do not close on desktop-only screenshots.
PlasticDigits commented 2026-08-29 08:04:47 +00:00 (Migrated from gitlab.com)

marked as related to #677

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

marked as related to #668

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

marked as related to #689

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

marked as related to #652

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

Skill / docs IDs for this issue: P703-1–P703-8 map 1:1 to AC M1–M8. Amend P668-4 (phone monthly limit ≤ 12) and P668-9 (Monthly on phone is fewer bars + YY-MM, not rotated ticks). Do not reopen #668 / #677 / #689.

Skill / docs IDs for this issue: **P703-1–P703-8** map 1:1 to AC **M1–M8**. Amend **P668-4** (phone monthly `limit` ≤ 12) and **P668-9** (Monthly on phone is fewer bars + `YY-MM`, not rotated ticks). Do not reopen #668 / #677 / #689.
PlasticDigits commented 2026-08-29 12:46:59 +00:00 (Migrated from gitlab.com)

mentioned in commit cec46e863f

mentioned in commit cec46e863fa683e2990e795d38fb399af946d3bd
PlasticDigits commented 2026-08-29 12:47:01 +00:00 (Migrated from gitlab.com)

mentioned in merge request !1196

mentioned in merge request !1196
PlasticDigits commented 2026-08-29 12:47:25 +00:00 (Migrated from gitlab.com)

Implemented in !1196.

M1–M8 covered in make verify-issue-703 (Vitest + Playwright phone 390 / tablet 820 collision, related 677/668/689). Phone Monthly limit is 6–12 newest UTC months; axis is YY-MM; ticks stay horizontal. Indexer GET max remains 24.

Remaining for operators: a Chrome 390×844 /protocol → Monthly (Volume then Liquidity / Fees) pass, and desktop 1280 Monthly still showing longer history.

Implemented in !1196. **M1–M8** covered in `make verify-issue-703` (Vitest + Playwright phone 390 / tablet 820 collision, related 677/668/689). Phone Monthly `limit` is 6–12 newest UTC months; axis is `YY-MM`; ticks stay horizontal. Indexer GET max remains 24. Remaining for operators: a Chrome 390×844 `/protocol` → Monthly (Volume then Liquidity / Fees) pass, and desktop 1280 Monthly still showing longer history.
PlasticDigits commented 2026-08-29 12:50:04 +00:00 (Migrated from gitlab.com)

mentioned in commit 5ff870ffa9

mentioned in commit 5ff870ffa9b2c2dd1f4ef04249033cb0db71cf33
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-08-29 12:50:04 +00:00
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#703
No description provided.