Skip to content

Commit e07c813

Browse files
authored
Merge pull request #3 from algoux/feat/ranklist-render-options
Add some new ranklist render options
2 parents 32ddcd1 + d87e078 commit e07c813

60 files changed

Lines changed: 10820 additions & 3914 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,37 @@
11
name: CI
22

33
on:
4+
workflow_dispatch:
45
pull_request:
56
push:
6-
branches:
7-
- master
87

98
permissions:
109
contents: read
1110

1211
jobs:
13-
release-check:
14-
name: Release checks
12+
test:
13+
name: Test packages
1514
runs-on: ubuntu-latest
1615

1716
steps:
1817
- name: Checkout repository
19-
uses: actions/checkout@v4
18+
uses: actions/checkout@v6
2019
with:
2120
fetch-depth: 0
2221

2322
- name: Setup Node.js
24-
uses: actions/setup-node@v4
23+
uses: actions/setup-node@v6
2524
with:
26-
node-version: 20
25+
node-version: 24
26+
package-manager-cache: false
2727

2828
- name: Setup pnpm
2929
run: |
3030
corepack enable
31-
corepack prepare pnpm@8.15.9 --activate
31+
corepack prepare pnpm@11.1.3 --activate
3232
3333
- name: Install dependencies
3434
run: pnpm install --frozen-lockfile
3535

36-
- name: Audit release plan
37-
if: github.event_name == 'pull_request' && !startsWith(github.head_ref, 'changeset-release/')
38-
run: pnpm -w run release:audit
39-
40-
- name: Run release verification
36+
- name: Run release verification tests
4137
run: pnpm -w run test:release

.github/workflows/release.yml

Lines changed: 17 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
name: Release
22

33
on:
4-
push:
4+
workflow_run:
5+
workflows:
6+
- CI
7+
types:
8+
- completed
59
branches:
610
- master
711
workflow_dispatch:
@@ -10,29 +14,33 @@ concurrency: ${{ github.workflow }}-${{ github.ref }}
1014

1115
permissions:
1216
contents: write
17+
id-token: write
1318
pull-requests: write
1419

1520
jobs:
1621
release:
1722
name: Version or publish packages
1823
runs-on: ubuntu-latest
24+
if: github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success'
1925

2026
steps:
2127
- name: Checkout repository
22-
uses: actions/checkout@v4
28+
uses: actions/checkout@v6
2329
with:
2430
fetch-depth: 0
31+
ref: ${{ github.event.workflow_run.head_sha || github.sha }}
2532

2633
- name: Setup Node.js
27-
uses: actions/setup-node@v4
34+
uses: actions/setup-node@v6
2835
with:
29-
node-version: 20
36+
node-version: 24
3037
registry-url: https://registry.npmjs.org
38+
package-manager-cache: false
3139

3240
- name: Setup pnpm
3341
run: |
3442
corepack enable
35-
corepack prepare pnpm@8.15.9 --activate
43+
corepack prepare pnpm@11.1.3 --activate
3644
3745
- name: Install dependencies
3846
run: pnpm install --frozen-lockfile
@@ -50,27 +58,12 @@ jobs:
5058
if: steps.pending-changesets.outputs.found == 'true'
5159
run: pnpm -w run release:audit
5260

53-
- name: Run release verification before version PR
54-
if: steps.pending-changesets.outputs.found == 'true'
55-
run: pnpm -w run test:release
56-
57-
- name: Configure npm token
61+
- name: Verify npm trusted publishing toolchain
5862
run: |
5963
set -euo pipefail
60-
61-
if [ -z "${NPM_TOKEN:-}" ]; then
62-
echo "NPM_TOKEN is not available to this workflow. Check repository/org secret name and repository access." >&2
63-
exit 1
64-
fi
65-
66-
echo "NPM_TOKEN length: ${#NPM_TOKEN}"
67-
printf '%s' "$NPM_TOKEN" | sha256sum | cut -c1-12 | sed 's/^/NPM_TOKEN sha256: /'
68-
69-
echo "npm userconfig: $(npm config get userconfig)"
70-
npm whoami --registry https://registry.npmjs.org/
71-
env:
72-
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
73-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
64+
node --version
65+
npm --version
66+
pnpm --version
7467
7568
- name: Create version PR or publish packages
7669
id: changesets
@@ -82,8 +75,6 @@ jobs:
8275
title: 'release: version packages'
8376
env:
8477
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
85-
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
86-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
8778

8879
- name: Create repository release baseline tag
8980
if: steps.changesets.outputs.published == 'true'

.node-version

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
24
Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
# Ranklist Render Options Props
2+
3+
This document is the agent-facing source of truth for the optional render props
4+
being piloted in the React package before promotion to the other framework
5+
packages.
6+
7+
## Execution Constraints
8+
9+
- Work happens on branch `feat/ranklist-render-options`.
10+
- Do not use a git worktree for this feature.
11+
- Do not create commits unless the user explicitly asks for a commit.
12+
- Preserve unrelated local changes.
13+
14+
## React Pilot API
15+
16+
Add the following optional props to React `RanklistProps`:
17+
18+
```ts
19+
splitOrganization?: boolean;
20+
columnTitles?: RanklistColumnTitles;
21+
statusCellPreset?: 'classic' | 'detailed' | 'minimal' | 'compact';
22+
statusColorAsText?: boolean;
23+
showProblemStatisticsFooter?: boolean;
24+
showDirtColumn?: boolean;
25+
showSEColumn?: boolean;
26+
rowBordered?: boolean;
27+
columnBordered?: boolean;
28+
emptyStatusPlaceholder?: string | null;
29+
userAvatarPlacement?: 'user' | 'organization';
30+
```
31+
32+
`RanklistColumnTitles` is text-only so the same concept can later map cleanly to
33+
Vue, Solid, Svelte, and Angular:
34+
35+
```ts
36+
interface RanklistColumnTitles {
37+
series?: string[] | ((series: srk.RankSeries, index: number) => string | undefined);
38+
organization?: string;
39+
user?: string;
40+
score?: string;
41+
time?: string;
42+
dirt?: string;
43+
se?: string;
44+
}
45+
```
46+
47+
Default labels are: series title from SRK, `Name`, `Organization`, `Score`,
48+
`Time`, `Dirt`, and `SE`.
49+
50+
## Prop Reference
51+
52+
| Prop | Default | Contract |
53+
| --- | --- | --- |
54+
| `splitOrganization` | `false` | Inserts an Organization column before User and hides organization text from the default User cell. |
55+
| `columnTitles` | SRK/default labels | Text-only title overrides for series, organization, user, score, time, dirt, and se columns. |
56+
| `statusCellPreset` | `classic` | Chooses the reusable status content preset: `classic`, `detailed`, `minimal`, or `compact`. |
57+
| `statusColorAsText` | `false` | Removes status fill backgrounds and uses bold status-colored text; FB adds a gold star marker. |
58+
| `showProblemStatisticsFooter` | `false` | Renders the multi-row problem statistics footer plus the final problem alias row. |
59+
| `showDirtColumn` | `false` | Appends the row Dirt percentage column after problem columns. |
60+
| `showSEColumn` | `false` | Appends the row SE column after problems and after Dirt when both are enabled. |
61+
| `rowBordered` | `false` | Enables row separators via shared CSS variables. Existing `borderedRows` remains a React alias. |
62+
| `columnBordered` | `false` | Enables column separators via shared CSS variables. |
63+
| `emptyStatusPlaceholder` | `null` | Replaces no-submission status cell blank content with a custom string. |
64+
| `userAvatarPlacement` | `user` | Moves the default avatar into the split Organization column only when set to `organization` and `splitOrganization` is enabled. |
65+
66+
## Behavior Rules
67+
68+
- `splitOrganization` inserts an Organization column between series columns and
69+
User. The default user cell must not duplicate organization text when the
70+
split column is active.
71+
- `userAvatarPlacement` defaults to `user`. `organization` only has an effect
72+
when `splitOrganization` is enabled; in that case the default avatar moves
73+
from the User column into the Organization column before the organization
74+
text. Without split organization, avatars stay in the User column.
75+
- `statusCellPreset` values:
76+
- `classic`: current rendering.
77+
- `detailed`: accepted line 1 is pass time, line 2 is `(-n)` when wrong tries
78+
exist. Rejected/frozen line 1 is an empty placeholder, line 2 is `(-tries)`
79+
when tries exist.
80+
- `minimal`: accepted is `+` or `+n`; rejected/frozen is `-n`.
81+
- `compact`: minimal first line plus pass time on a second line for accepted
82+
cells. Rejected/frozen cells render `-n`; when `tries > 0` and `solutions`
83+
contains at least one penalty-bearing solution after filtering
84+
`sorter.config.noPenaltyResults`, render the last such solution time on a
85+
second line. If no penalty-bearing solution exists, keep the single `-n`
86+
line.
87+
- No-submission statuses remain blank in all presets.
88+
- `emptyStatusPlaceholder` changes only no-submission status cells (`result:
89+
null`). Its default is `null`, which preserves the blank cell behavior.
90+
- Status time formatting uses ICPC sorter `config.timePrecision` when present,
91+
otherwise the status time unit. Minute or coarser displays `h:mm`, seconds
92+
displays `h:mm:ss`, milliseconds displays `h:mm:ss.SSS`.
93+
- `statusColorAsText` removes status fill backgrounds and uses bold colored
94+
text for status emphasis. FB cells additionally render a gold star inside the
95+
cell near the top-right corner.
96+
- Footer statistics use `status.tries` as the valid submission source:
97+
- Accepted: number of users whose status result is `AC` or `FB`.
98+
The cell shows a second line `(r%)`, where `r` is
99+
`Math.floor(accepted / participantCount * 100)`. If the participant count
100+
is `0`, show `(-)`.
101+
- Tried: number of users with `tries > 0`.
102+
The cell shows a second line `(r%)`, where `r` is
103+
`Math.floor(tried / participantCount * 100)`. If the participant count is
104+
`0`, show `(-)`.
105+
- Submitted: sum of `tries`.
106+
- Dirt: among users who accepted this problem (`AC` or `FB`), sum
107+
`max(tries - 1, 0)` as the first line. The second line is `(r%)`, where
108+
`r` is `Math.floor(dirt / acceptedSubmitted * 100)` and
109+
`acceptedSubmitted` is the sum of `tries` for accepted users. If
110+
`acceptedSubmitted` is `0`, show `(-)`.
111+
- SE: average hardness, formatted with two decimals as
112+
`(participantCount - accepted) / participantCount` using round-to-nearest
113+
formatting. If the participant count is `0`, show `-`.
114+
- FB at and LB at are separate rows, formatted as floored minute integers.
115+
- Footer label rows use the shared marker tooltip style with these English
116+
explanations. The tooltip class must be attached to the inner label text
117+
element, not the full footer row, so the tooltip is anchored on the text
118+
itself. Footer statistic tooltips are positioned to the left of their labels
119+
and the tooltip pseudo-element must not receive pointer events, otherwise the
120+
hover hot zone can drift to the tooltip bubble:
121+
- Accepted: number of participants who solved this problem
122+
- Tried: number of participants who attempted this problem
123+
- Submitted: total number of valid submissions for this problem
124+
- Dirt: wrong submissions among participants who solved this problem
125+
- SE: average hardness, calculated as `(participants - accepted) / participants`
126+
- FB at: First Blood at, also known as first solve time, in minutes
127+
- LB at: Last Blood at, also known as last solve time, in minutes
128+
- Footer statistics must be structured as real table rows: one `<tr>` per
129+
statistic item, with the left label cell spanning all non-problem columns and
130+
each problem value in its own cell. Do not stack all statistic rows inside a
131+
single footer cell; that breaks label/value alignment and prevents footer
132+
rows from naturally following row striping and row border styles. Row striping
133+
and row borders apply only to per-problem statistic value cells; do not apply
134+
them to the left label cell or to right-side alignment-only extra cells such
135+
as the Dirt or SE footer cells.
136+
- Footer statistics end with one extra problem label row after `LB at`. Its
137+
per-problem cells render only the problem alias (or the alphabet fallback)
138+
and reuse the problem-header background treatment with the gradient direction
139+
reversed by 180 degrees, without the header's accepted-count second line.
140+
Their background is clipped to the padding box so transparent row-border
141+
space does not show a colored top edge when row borders are disabled.
142+
- Dirt only considers accepted problems. Numerator is sum of `tries - 1`;
143+
denominator is sum of `tries`; percentages are floored integers and zero
144+
denominator renders `0%`.
145+
- `showSEColumn` appends a contestant `SE` column after the problem columns.
146+
When `showDirtColumn` is also enabled, `SE` is placed after `Dirt`.
147+
Contestant SE is the average of the per-problem SE values for every problem
148+
the row accepted (`AC` or `FB`). A row with no accepted problems renders
149+
`0.00`. Contestant SE uses the same two-decimal round-to-nearest formatting
150+
as the footer SE row.
151+
- `Score`, `Time`, `Dirt`, and `SE` column headers are right-aligned to match
152+
their numeric body cells.
153+
- If the footer and appended extra columns are both enabled, the footer gets
154+
one empty alignment cell for each enabled extra column, in column order
155+
(`Dirt`, then `SE`).
156+
- `rowBordered` enables a light horizontal separator between body rows.
157+
`borderedRows` remains supported as the existing React alias. The color is
158+
controlled by `--srk-table-row-border-color`, which defaults to
159+
`--srk-table-border` only when row borders are enabled.
160+
- `columnBordered` enables light vertical separators between columns. The color
161+
is controlled by `--srk-table-column-border-color`, which defaults to
162+
`--srk-table-border` only when column borders are enabled. Column separator
163+
border rules must be scoped behind `.srk-table-column-bordered`; transparent
164+
borders must not be emitted for the disabled state. Use an inset separator
165+
rather than collapsed table borders so sticky problem headers and body cells
166+
render the same separator color. Footer alignment-only extra cells for
167+
appended columns (`Dirt`, `SE`) must not render column separators.
168+
- Series segment markers are visual-only overlays, not real table borders.
169+
They must use an inner pseudo-element driven by
170+
`--srk-series-segment-border-width` and `--srk-series-segment-color`, so
171+
`columnBordered` separators stay aligned with headers and non-segment rows.
172+
Any series column that can display a preset segment marker must reserve
173+
right-side content padding for the whole column with
174+
`srk-series-segmented-column` and `--srk-series-segment-content-gap`;
175+
otherwise rows without an active marker no longer align with rows that do
176+
have one. When row borders are disabled, preset segment marker pseudo-elements
177+
also bleed across the transparent 1px row boundary with
178+
`--srk-series-segment-row-bleed`; `.srk-table-row-bordered` resets this bleed
179+
to `0px` so real row separators remain visible.
180+
- Problem header cells keep an opaque base background under their existing
181+
problem-style gradient and sit above body rows while sticky, so status text
182+
cannot show through during scroll.
183+
- Empty status placeholders use a centered placeholder cell class and must not
184+
inherit any accepted/failed/frozen/FB status highlight class.
185+
186+
## Rollout Status
187+
188+
- React: implemented in source; focused option tests, `pnpm test:react`,
189+
`pnpm build:styles`, `pnpm build:core`, and `pnpm build:react` have been
190+
run during the pilot.
191+
Latest feedback pass adds footer tooltip labels, wider footer row spacing,
192+
row/column border props, empty status placeholders, text-anchored footer
193+
tooltips, scoped column border CSS, and sticky problem header bleed-through
194+
protection. Follow-up QA changed column separators to inset shadows and moved
195+
footer statistic tooltips to the left side with pointer-events disabled.
196+
Latest compact preset update adds rejected/frozen second-line penalty solution
197+
time when `solutions` data can identify a last effective wrong submission.
198+
Latest avatar placement update adds `userAvatarPlacement` for moving the
199+
default avatar into the split Organization column. Latest series segment pass
200+
keeps marker bars out of table border geometry so column separators align
201+
when `columnBordered` is enabled, while preserving the old text-to-marker
202+
spacing across the whole affected series column and preventing row-gap breaks
203+
when row borders are disabled. Latest footer statistics update adds Accepted
204+
and Tried percentages, per-problem Dirt and SE rows, and renames FB/LB labels
205+
to `FB at` / `LB at`. Latest footer layout pass renders each statistic as its
206+
own table row so label/value alignment is handled by the table layout itself,
207+
and scopes footer striping/borders to problem statistic value cells only.
208+
Latest SE column update adds `showSEColumn`, keeps appended extra columns in
209+
`Dirt` then `SE` order, and standardizes SE formatting to two-decimal
210+
round-to-nearest output for both footer and contestant values. Final React
211+
pilot cleanup extracts status preset presentation into core and documents the
212+
prop contracts, shared implementation map, and test coverage matrix for the
213+
upcoming framework ports.
214+
- Vue, Solid, Svelte, Angular: pending React manual confirmation.
215+
216+
## React Implementation Notes
217+
218+
- Runtime helpers for shared logic live in core so later framework ports can use
219+
the same time formatting, status preset presentation, footer statistics,
220+
problem header background gradients, Dirt, and SE calculation rules.
221+
- Shared CSS for row/column borders, status color-as-text, FB star markers,
222+
split Organization/avatar layout, series segment bars, footer rows, footer
223+
tooltips, and footer problem alias cells lives in the styles package.
224+
- Framework packages should keep only framework rendering/composition local.
225+
Any portable value calculation or formatting needed by multiple renderers
226+
should be added to core before ports proceed.
227+
- The root Vitest workspace aliases the core package to `packages/core/src` so
228+
tests exercise source changes instead of stale local `dist` artifacts.
229+
- React still imports core by package name for normal builds; build order remains
230+
core before React.
231+
232+
## Test Coverage
233+
234+
- React option tests cover split organization, custom titles, right-aligned
235+
numeric/extra headers, avatar placement, all status presets, sorter precision
236+
time formatting, compact rejected penalty solution time, color-as-text FB star,
237+
footer statistics rows, footer tooltips, footer problem alias row, Dirt, SE,
238+
empty placeholders, and row/column border classes.
239+
- Structure tests cover shared CSS selector contracts for gated column borders,
240+
footer extra-cell border exclusions, opaque problem headers, footer problem
241+
alias row styling, left-positioned tooltips, footer striping/row borders, and
242+
segment marker geometry.
243+
- React dev tests cover the local `dev:react` controls for the new props,
244+
Showcase/Baseline presets, and the default modal wiring remains intact.

0 commit comments

Comments
 (0)