From e7fc8cb9f19d96670053a5b6bd9546742c0b3232 Mon Sep 17 00:00:00 2001 From: DemolaCodes Date: Sun, 18 Oct 2026 22:02:42 +0100 Subject: [PATCH 1/8] fix(referrals): display distinct hourly labels and stable chart keys (#848) Use 24h clock labels without repeating exact-window endpoints; key the fixed chart ticks by position. --- .../referrals/components/shared/stat-chart-card.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/web/src/features/referrals/components/shared/stat-chart-card.tsx b/apps/web/src/features/referrals/components/shared/stat-chart-card.tsx index 490f42b1..bfbb49e6 100644 --- a/apps/web/src/features/referrals/components/shared/stat-chart-card.tsx +++ b/apps/web/src/features/referrals/components/shared/stat-chart-card.tsx @@ -5,8 +5,11 @@ import { formatUsd } from "@/shared/lib/format" function xAxisLabels(period: TimePeriod): Array { const now = new Date() + const pad = (value: number) => String(value).padStart(2, "0") const fmt = (d: Date) => - `${String(d.getMonth() + 1).padStart(2, "0")}/${String(d.getDate()).padStart(2, "0")}` + period === "24h" + ? `${pad(d.getHours())}:${pad(d.getMinutes())}` + : `${pad(d.getMonth() + 1)}/${pad(d.getDate())}` const msAgo = (ms: number) => new Date(now.getTime() - ms) const day = 86_400_000 @@ -18,7 +21,9 @@ function xAxisLabels(period: TimePeriod): Array { total: 365 * day, } - const total = ranges[period] + // Exact 24-hour endpoints share a clock time. Exclude the first minute + // so five HH:mm labels are meaningful and distinct without adding a date. + const total = period === "24h" ? ranges[period] - 60_000 : ranges[period] return Array.from({ length: 5 }, (_, i) => fmt(msAgo(total - (i / 4) * total))) } @@ -118,7 +123,7 @@ export function StatChartCard({ title, tooltip, value, period, accent = "blue" } const x = 28 + (i / (labels.length - 1)) * 368 return ( Date: Sun, 18 Oct 2026 22:02:51 +0100 Subject: [PATCH 2/8] test(referrals): guard unique axis labels across all periods (#848) --- .../shared/stat-chart-card.test.tsx | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/apps/web/src/features/referrals/components/shared/stat-chart-card.test.tsx b/apps/web/src/features/referrals/components/shared/stat-chart-card.test.tsx index 4fb7d007..c59b51fe 100644 --- a/apps/web/src/features/referrals/components/shared/stat-chart-card.test.tsx +++ b/apps/web/src/features/referrals/components/shared/stat-chart-card.test.tsx @@ -1,9 +1,39 @@ -import { afterEach, describe, expect, it } from "vitest" +import { afterEach, describe, expect, it, vi } from "vitest" import { cleanup, render, screen } from "@testing-library/react" import { StatChartCard } from "./stat-chart-card" describe("StatChartCard", () => { - afterEach(cleanup) + afterEach(() => { + cleanup() + vi.useRealTimers() + }) + + it.each(["24h", "7d", "30d", "90d", "total"] as const)( + "renders five distinct labels for the %s period", + (period) => { + vi.useFakeTimers() + vi.setSystemTime(new Date("2026-10-15T12:30:00")) + + const { container } = render( + , + ) + const labels = Array.from(container.querySelectorAll('text[y="100"]')).map( + (element) => element.textContent, + ) + + expect(labels).toHaveLength(5) + expect(new Set(labels).size).toBe(labels.length) + if (period === "24h") { + expect(labels).toEqual( + expect.arrayContaining([expect.stringMatching(/^\\d{2}:\\d{2}$/)]), + ) + expect(labels.every((label) => /^\\d{2}:\\d{2}$/.test(label ?? ""))).toBe(true) + } else { + expect(labels.every((label) => /^\\d{2}\\/\\d{2}$/.test(label ?? ""))).toBe(true) + } + }, + ) + it("renders the title", () => { render( , From 13f87ba160352ce6181289d18c7db1d70e5d516e Mon Sep 17 00:00:00 2001 From: DemolaCodes Date: Sun, 18 Oct 2026 22:06:30 +0100 Subject: [PATCH 3/8] ci(test): run full contribution gate for issue branch --- .../workflows/contribution-verification.yml | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 .github/workflows/contribution-verification.yml diff --git a/.github/workflows/contribution-verification.yml b/.github/workflows/contribution-verification.yml new file mode 100644 index 00000000..03344d11 --- /dev/null +++ b/.github/workflows/contribution-verification.yml @@ -0,0 +1,54 @@ +name: Contribution verification +on: + push: + branches: + - fix/issue-837-pin-dependency-ranges + - ci/issue-838-dependency-security + - fix/issue-846-canonical-brand + - fix/issue-848-referral-chart-labels +jobs: + full-gate: + runs-on: ubuntu-latest + timeout-minutes: 40 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: oven-sh/setup-bun@v2 + with: + bun-version: 1.3.13 + - name: Install exact locked dependencies + run: bun install --frozen-lockfile + - name: Lint + run: bun lint + - name: Typecheck + run: bun typecheck + - name: Design tokens + run: bun run check:tokens + - name: Docs content + run: bun run check:content + - name: Tests + run: bun run test + - name: Coverage + run: bun run test:coverage + - name: Production build + run: bun run build + - name: Indexer generation and build + run: bun run --cwd apps/s03-indexer codegen && bun run --cwd apps/s03-indexer build + - name: Indexer test + run: bun run --cwd apps/s03-indexer test + - name: Indexer local manifest sync + run: SO4_CONTRACTS_REPO="$PWD/apps/s03-indexer/tests/fixtures/contracts-repo" bun run --cwd apps/s03-indexer sync:contracts:local && bash scripts/validate-manifest.sh apps/s03-indexer/config/contracts.local.json + - name: Web typecheck + run: bun run --cwd apps/web typecheck + - name: Web build + run: bun run --cwd apps/web build + audit: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v2 + with: + bun-version: 1.3.13 + - run: bun audit --audit-level=high From db274fefa252038bde5de078da52c96b8166b633 Mon Sep 17 00:00:00 2001 From: DemolaCodes Date: Sun, 18 Oct 2026 22:06:53 +0100 Subject: [PATCH 4/8] test: correct regex literals in issue-specific coverage --- .../referrals/components/shared/stat-chart-card.test.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/web/src/features/referrals/components/shared/stat-chart-card.test.tsx b/apps/web/src/features/referrals/components/shared/stat-chart-card.test.tsx index c59b51fe..77858c7e 100644 --- a/apps/web/src/features/referrals/components/shared/stat-chart-card.test.tsx +++ b/apps/web/src/features/referrals/components/shared/stat-chart-card.test.tsx @@ -25,11 +25,11 @@ describe("StatChartCard", () => { expect(new Set(labels).size).toBe(labels.length) if (period === "24h") { expect(labels).toEqual( - expect.arrayContaining([expect.stringMatching(/^\\d{2}:\\d{2}$/)]), + expect.arrayContaining([expect.stringMatching(/^\d{2}:\d{2}$/)]), ) - expect(labels.every((label) => /^\\d{2}:\\d{2}$/.test(label ?? ""))).toBe(true) + expect(labels.every((label) => /^\d{2}:\d{2}$/.test(label ?? ""))).toBe(true) } else { - expect(labels.every((label) => /^\\d{2}\\/\\d{2}$/.test(label ?? ""))).toBe(true) + expect(labels.every((label) => /^\d{2}\/\d{2}$/.test(label ?? ""))).toBe(true) } }, ) From c13cce3dcb8eead6b5f6d9be6be3f85ccab3d291 Mon Sep 17 00:00:00 2001 From: DemolaCodes Date: Sun, 18 Oct 2026 22:07:52 +0100 Subject: [PATCH 5/8] test(referrals): remove unnecessary fallback in label assertions (#848) --- .../referrals/components/shared/stat-chart-card.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/web/src/features/referrals/components/shared/stat-chart-card.test.tsx b/apps/web/src/features/referrals/components/shared/stat-chart-card.test.tsx index 77858c7e..339d5432 100644 --- a/apps/web/src/features/referrals/components/shared/stat-chart-card.test.tsx +++ b/apps/web/src/features/referrals/components/shared/stat-chart-card.test.tsx @@ -27,9 +27,9 @@ describe("StatChartCard", () => { expect(labels).toEqual( expect.arrayContaining([expect.stringMatching(/^\d{2}:\d{2}$/)]), ) - expect(labels.every((label) => /^\d{2}:\d{2}$/.test(label ?? ""))).toBe(true) + expect(labels.every((label) => /^\d{2}:\d{2}$/.test(label))).toBe(true) } else { - expect(labels.every((label) => /^\d{2}\/\d{2}$/.test(label ?? ""))).toBe(true) + expect(labels.every((label) => /^\d{2}\/\d{2}$/.test(label))).toBe(true) } }, ) From 7686115ab20a32a0da83d5714fdb8984daf571f3 Mon Sep 17 00:00:00 2001 From: DemolaCodes Date: Sun, 18 Oct 2026 22:08:53 +0100 Subject: [PATCH 6/8] ci(test): collect independent gate results without masking any failure --- .github/workflows/contribution-verification.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/contribution-verification.yml b/.github/workflows/contribution-verification.yml index 03344d11..66847423 100644 --- a/.github/workflows/contribution-verification.yml +++ b/.github/workflows/contribution-verification.yml @@ -20,28 +20,40 @@ jobs: - name: Install exact locked dependencies run: bun install --frozen-lockfile - name: Lint + if: ${{ success() || failure() }} run: bun lint - name: Typecheck + if: ${{ success() || failure() }} run: bun typecheck - name: Design tokens + if: ${{ success() || failure() }} run: bun run check:tokens - name: Docs content + if: ${{ success() || failure() }} run: bun run check:content - name: Tests + if: ${{ success() || failure() }} run: bun run test - name: Coverage + if: ${{ success() || failure() }} run: bun run test:coverage - name: Production build + if: ${{ success() || failure() }} run: bun run build - name: Indexer generation and build + if: ${{ success() || failure() }} run: bun run --cwd apps/s03-indexer codegen && bun run --cwd apps/s03-indexer build - name: Indexer test + if: ${{ success() || failure() }} run: bun run --cwd apps/s03-indexer test - name: Indexer local manifest sync + if: ${{ success() || failure() }} run: SO4_CONTRACTS_REPO="$PWD/apps/s03-indexer/tests/fixtures/contracts-repo" bun run --cwd apps/s03-indexer sync:contracts:local && bash scripts/validate-manifest.sh apps/s03-indexer/config/contracts.local.json - name: Web typecheck + if: ${{ success() || failure() }} run: bun run --cwd apps/web typecheck - name: Web build + if: ${{ success() || failure() }} run: bun run --cwd apps/web build audit: runs-on: ubuntu-latest From abed69dc7913670a8b29d2c520036d9dd386bd95 Mon Sep 17 00:00:00 2001 From: DemolaCodes Date: Sun, 18 Oct 2026 22:14:13 +0100 Subject: [PATCH 7/8] docs(changelog): record accessible referral-chart time labels (#848) --- .../unreleased/9997-referral-chart-hourly-labels.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .changelog/unreleased/9997-referral-chart-hourly-labels.md diff --git a/.changelog/unreleased/9997-referral-chart-hourly-labels.md b/.changelog/unreleased/9997-referral-chart-hourly-labels.md new file mode 100644 index 00000000..e6dd9bb1 --- /dev/null +++ b/.changelog/unreleased/9997-referral-chart-hourly-labels.md @@ -0,0 +1,8 @@ +--- +type: fixed +area: referrals +pr: 9997 +breaking: false +--- + +Referral charts now display distinct hourly labels for the 24-hour view without duplicate chart keys or repeated dates. From cec68a739c240ba8accd2a5400a4f2eb5eaebc01 Mon Sep 17 00:00:00 2001 From: DemolaCodes Date: Sun, 18 Oct 2026 22:15:09 +0100 Subject: [PATCH 8/8] chore: remove temporary CI verification workflow before upstream contribution --- .../workflows/contribution-verification.yml | 66 ------------------- 1 file changed, 66 deletions(-) delete mode 100644 .github/workflows/contribution-verification.yml diff --git a/.github/workflows/contribution-verification.yml b/.github/workflows/contribution-verification.yml deleted file mode 100644 index 66847423..00000000 --- a/.github/workflows/contribution-verification.yml +++ /dev/null @@ -1,66 +0,0 @@ -name: Contribution verification -on: - push: - branches: - - fix/issue-837-pin-dependency-ranges - - ci/issue-838-dependency-security - - fix/issue-846-canonical-brand - - fix/issue-848-referral-chart-labels -jobs: - full-gate: - runs-on: ubuntu-latest - timeout-minutes: 40 - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - uses: oven-sh/setup-bun@v2 - with: - bun-version: 1.3.13 - - name: Install exact locked dependencies - run: bun install --frozen-lockfile - - name: Lint - if: ${{ success() || failure() }} - run: bun lint - - name: Typecheck - if: ${{ success() || failure() }} - run: bun typecheck - - name: Design tokens - if: ${{ success() || failure() }} - run: bun run check:tokens - - name: Docs content - if: ${{ success() || failure() }} - run: bun run check:content - - name: Tests - if: ${{ success() || failure() }} - run: bun run test - - name: Coverage - if: ${{ success() || failure() }} - run: bun run test:coverage - - name: Production build - if: ${{ success() || failure() }} - run: bun run build - - name: Indexer generation and build - if: ${{ success() || failure() }} - run: bun run --cwd apps/s03-indexer codegen && bun run --cwd apps/s03-indexer build - - name: Indexer test - if: ${{ success() || failure() }} - run: bun run --cwd apps/s03-indexer test - - name: Indexer local manifest sync - if: ${{ success() || failure() }} - run: SO4_CONTRACTS_REPO="$PWD/apps/s03-indexer/tests/fixtures/contracts-repo" bun run --cwd apps/s03-indexer sync:contracts:local && bash scripts/validate-manifest.sh apps/s03-indexer/config/contracts.local.json - - name: Web typecheck - if: ${{ success() || failure() }} - run: bun run --cwd apps/web typecheck - - name: Web build - if: ${{ success() || failure() }} - run: bun run --cwd apps/web build - audit: - runs-on: ubuntu-latest - timeout-minutes: 15 - steps: - - uses: actions/checkout@v4 - - uses: oven-sh/setup-bun@v2 - with: - bun-version: 1.3.13 - - run: bun audit --audit-level=high