Commit 9bb059d
Fixes #19046
Clause-②: yes
The `object-grid` page-component door declared `pagination: z.unknown()`
and `pageSize: z.number()`, so the same authored member carried **two
accept sets** and renderers read the looser one. This bounds the
page-size members to the accept set the view arm has ruled all along,
and deliberately leaves the `pagination` bag open.
## The premise, re-derived by symbol at this branch's base (`362035cc0`)
⛔ No line number inherited from the card — triage warned about exactly
that, and the card's own reading was taken on `abb01f1`.
| arm | symbol | declaration at my base | accepts `0`? |
|:--|:--|:--|:--|
| view | `PaginationConfigSchema`
(`packages/spec/src/ui/view.zod.ts:867-868`) | `pageSize:
z.number().int().positive().default(25)` · `pageSizeOptions:
z.array(z.number().int().positive()).optional()` | no |
| grid component | `ObjectGridPropsSchema`
(`packages/spec/src/ui/component.zod.ts:2632`, `:2634`) | `pagination:
z.unknown().optional()` · `pageSize: z.number().optional()` | **yes —
both** |
The view arm's refusals are pinned **by name** (`view.test.ts` — `should
reject negative pageSize`, `should reject zero pageSize`, and the same
pair for `pageSizeOptions`). The corpus corroboration also holds at my
base — every other page-size declaration in the package is bounded:
```
packages/spec/src/ui/view.zod.ts:867 z.number().int().positive().default(25)
packages/spec/src/ui/view.zod.ts:868 z.array(z.number().int().positive())
packages/spec/src/ui/component.zod.ts:2634 z.number() ** the outlier
packages/spec/src/marketplace/marketplace.zod.ts:435 z.number().int().min(1).max(100).default(20)
packages/spec/src/marketplace/marketplace.zod.ts:456 z.number().int().min(1)
packages/spec/src/kernel/metadata-plugin.zod.ts:399 z.number().int().min(1).max(500).default(50)
packages/spec/src/kernel/metadata-plugin.zod.ts:429 z.number().int().min(1)
```
PR #18638, which held this file, is merged (2026-09-18T16:01:37Z) and
did **not** tighten it in passing, so triage's downgrade clause does not
apply.
## The shape decision — a permissive object, and the evidence that chose
it
The card's complaint is that the two arms disagree about a page
**size**. It is ⛔ not that `pagination` should become a closed shape.
Two shapes were plausible; the evidence is one-sided.
**Chosen: `z.looseObject({ pageSize, pageSizeOptions })`** — validates
the two declared members, passes every other key through.
**Rejected: `z.unknown()` plus a refinement judging only `pageSize`.**
It looks more conservative and is measurably worse here:
- `z.toJSONSchema()` has **no arm for a `custom` check**. A record, the
same record with a `.refine()`, and the same record with an aborting
`.refine()` all project byte-identically — the mechanism
`packages/spec/dropped-refinements.baseline.json` exists to record. A
refinement would have left the **published** JSON Schema still accepting
`pageSize: 0` while the parser refused it, and it would have needed a
**new row in that shrink-only ledger**, which is a ratchet this dev may
not raise.
- The loose object is a **type** narrowing, so it projects. Measured on
the built artifact:
```
packages/spec/json-schema/ui/ObjectGridProps.json
pagination.properties.pageSize { "type": "integer", "exclusiveMinimum": 0 }
pagination.properties.pageSizeOptions.items { "type": "integer", "exclusiveMinimum": 0 }
pagination.additionalProperties {} ** the bag stays OPEN
pageSize { "type": "integer", "exclusiveMinimum": 0 }
```
`dropped-refinements.baseline.json` is **untouched** by this PR:
`ui/ObjectGridProps` keeps its single pre-existing `filter.element` site
and gains none.
**Read points, measured at objectui `d18322415`** (the sibling checkout
in this container; the `.objectui-sha` pin is `53ded82bf`):
`ObjectGrid.tsx:1209` and `:1628` read `(schema.pagination as
any)?.pageSize ?? schema.pageSize`, `:4179` reads
`schema.pagination?.pageSize`, `:4359` reads
`schema.pagination?.pageSizeOptions`. Across objectui's whole source,
`pageSize` and `pageSizeOptions` are the **only two members** any
`pagination` read point names (37 + 6 reads of `.pageSize`, 7 + 3 of
`.pageSizeOptions`, zero of anything else). The objectui registry
declares this input `type: 'object'` (`plugin-grid/src/index.tsx:223`).
### What was NOT narrowed, and why
- **Sibling keys inside the bag.** `z.looseObject`, not `strictObject`:
a sibling key that parsed before still parses **and still survives the
parse byte-identically**. Reusing `PaginationConfigSchema` here would
have refused every one of them — the `…` in this door's own describe
says authors write them — which is a wider breaking change than the
card's premise and a different decision. §3 of the new pin is what makes
that auditable; §4 records the deliberate asymmetry (the view arm stays
closed, this bag stays open), so a future author harmonising the two
arms reds a case instead of discovering the consequence in a renderer.
- **No `.default(25)` added to the flat shorthand.** The view arm has
one; adding one here would change parsed output, not the accept set.
- **`pageSizeOptions` WAS bounded, and that is a judgement I am naming
rather than burying.** It is the same defect class by a second door:
`pageSizeOptions: [0, 25]` puts a zero entry in the page-size selector,
which sets the fetch window to zero rows — the card's exact failure. Its
shape was already pinned by the view arm
(`z.array(z.number().int().positive())`), whose zero/negative refusals
are pinned by name, and its read point is measured above. Corpus cost:
zero `pageSizeOptions` entries outside the spec's own refusal fixtures
are non-positive.
### One second axis, stated rather than left to be discovered
`pagination` moves from `z.unknown()` to an object type, so a non-object
value (`pagination: true`) is refused where it used to parse. Measured
before narrowing:
- **zero** non-object `pagination` values on an `object-grid` node in
either repository (the `pagination: false` hits in objectui are on
`data-table` / `object-data-table`, whose props this schema does not
declare, plus one internal per-group table the grid builds itself at
`ObjectGrid.tsx:4590`);
- the registry has published `type: 'object'` all along, so the html
tier already answered `type-mismatch` on one while this schema accepted
it — the same shape the `sort` docblock two members up already records;
- `ObjectGrid.tsx:4175` reads the key for **presence**
(`schema.pagination !== undefined ? true : …`), which means an authored
`pagination: false` used to turn paging **ON**. That value now gets a
located refusal instead of the opposite of what it says.
## Pins, each with its control
New file:
`packages/spec/src/ui/component-object-grid-pagination-accept-set.pin.test.ts`
— 19 cases, 4 sections.
| section | asserts | control |
|:--|:--|:--|
| §1 | `pagination.pageSize` refuses zero / negative / non-integer, and
`pageSizeOptions` entries refuse zero / negative — each asserting the
issue **code and path** (`too_small` at `pagination.pageSize`), not a
bare throw | two LIT CONTROLS: a legal `pageSize` parses and is
preserved; the whole ruled bag parses with its options |
| §2 | the flat shorthand carries the same accept set, by name | a LIT
CONTROL: `pageSize: 25` parses and keeps its value |
| §3 | a sibling key in the bag parses with **no `unrecognized_keys`
issue**, survives byte-identically (`toStrictEqual`), and a bag of only
sibling keys parses | this section IS the control for the trap above |
| §4 | both arms refuse the same three non-page-sizes, and both accept
`50` | an unknown KEY is refused by the view arm (`unrecognized_keys`)
and accepted by the component bag — the asymmetry, pinned |
**Defect reproduced in this tree, then the refusal proved able to
fail.** Ablation through `scripts/ablation-replace.mjs`, anchor `const
GridPageSizeSchema = z.number().int().positive();` replaced by `const
GridPageSizeSchema = z.number();` (the pre-PR accept set), from the
committed state:
```
ablation-replace: ok mutation landed: anchor 1 -> 0, blob d9e4dec -> 462c333a1bda
Test Files 1 failed (1)
Tests 11 failed | 8 passed (19)
FAIL §1 ... > should reject zero pageSize
AssertionError: expected true to be false ** parse({ pagination: { pageSize: 0 } }) SUCCEEDS
ablation-replace: ok restored: blob == HEAD (d9e4dec) and `git diff HEAD` is empty
```
The 11 that reddened are exactly §1/§2/§4's refusals; the 8 that stayed
green are the lit controls and §3's openness pins — the right partition,
since the ablation removed only the value bound. Restored again through
the explicit form: `git checkout HEAD --
packages/spec/src/ui/component.zod.ts`, then `git hash-object` equal to
`git rev-parse HEAD:` that path (`d9e4decd6443…`), `git diff HEAD` empty
and `git status --porcelain` empty — and the pin re-run green (19/19)
from the restored tree.
## Changeset — the derivation, quoting the rule
`.changeset/19046-object-grid-page-size-accept-set.md` grades
`@objectstack/spec: minor`, carries the BREAKING banner, `Clause-②: yes
(narrowing)`, a FROM → TO table and the ADR-0087 disposition.
- `scripts/check-changeset-no-major.mjs` header: **"During the launch
window we ship breaking changes as `minor`"**, and its end condition —
**"at GA … an accept-set narrowing … grades `major`. Until then it is
NOT the carrier"** — with `major` refused outright by the guard. So the
rule does ⛔ not point at `major`, and there is nothing here for the
maintainer floor to rule on.
- `pr-automation.yml` "WHICH LEVEL": a widening takes at least `minor`,
and the level axis refuses `patch` across the board on a PR that
declares clause ②. Declaring `Clause-②: yes` therefore forces at least
`minor` — which is where the launch-window rule already put it.
- Direction carriers, per the same header: the **BREAKING banner** plus
the **ADR-0087 disposition**. Disposition is `registered
ui-object-grid-page-size-positive-integer-refused`, a new semantic entry
— the four `not-required` categories are all refused by construction
here (`unpublished`: spec publishes; `no-migration-prescription` and
`runtime-interface-only`: the body carries a FROM → TO table, and "a
changeset that ships instructions for rewriting a consumer's code cannot
also claim that no consumer has to rewrite anything";
`type-surface-only`: this is a runtime accept set on a metadata surface,
not a type annotation).
- `skip-changeset` was never available: this moves a published accept
set on a package that ships.
Verdicts: `check-changeset-no-major.mjs` exit **0**;
`check-adr-0087-registration.mjs` exit **0** — `1 declared-breaking
changeset(s), each carrying an ADR-0087 disposition`.
## Verification
Full census derived from the real change set after the changeset
existed, at `8ecc9b6ed`, with every exit code captured **before** any
pipe:
```
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack
-> 8 path(s) vs merge base 07c6f82, three-dot; 109 commands
107 exit 0 · 2 PREREQUISITE NOT MET (exit 3) · 0 findings
```
The two that could not run, neither a pass nor a finding:
| family | reason | what it needs |
|:--|:--|:--|
| `pnpm check:dual-build-cjs-loads` | `PREREQUISITE NOT MET — this gate
reads built output, and some package has no dist/` (34 packages) | a
repo-wide `pnpm build`; CI's `Build Core` supplies it |
| `pnpm check:type-check-debt` | `--re-measure cannot run: 1 workspace
dependenc(ies) … have no built type entry point on disk —
@objectstack/driver-turso` | `turbo run build --filter='./packages/*'
--filter='./packages/*/*'`, as lint.yml does |
Four families reported `PREREQUISITE NOT MET` or a missing input on
first run and were then **made to run** rather than declared:
`check:doc-formula-expressions` and `check:doc-security-posture` (needed
`@objectstack/formula` + `@objectstack/lint` built) and
`check:skill-examples` (needed `@objectstack/client-react`'s closure)
all became exit 0; `check:react-declaration-parity` was run as CI runs
it (`MANIFEST="$PWD/sdui.manifest.json" … --strict`) and reports **no
new declaration divergence vs the accepted baseline**.
Beyond the census:
- `pnpm --filter @objectstack/spec test` — **495 files / 14539 tests
pass** (post-merge); `typecheck` green, test-layer ledger unmoved at 54
files / 259 errors / 144 pinned signatures.
- `pnpm --filter @objectstack/spec check:generated` — **all 16 generated
artifacts up to date**. Three were proved stale and regenerated with
`--fix` only (`api-surface-declarations/`, `content/docs/references/**`,
the strictness-ledger counts); the `authorable-surface.base.json` anchor
was never touched.
- The one in-repo consumer of the changed surface is `packages/lint`
(`ComponentPropsMap`, `@objectstack/spec/ui`): `typecheck` green with
its ledger unmoved (2 files / 6 errors / 2 pinned), `test` **104 files /
3910 tests pass**. No corpus fixture anywhere in `examples/`, `apps/` or
another package authors `pagination` on an `object-grid` node, so
nothing in the tree newly fails to parse.
- Repo-wide `pnpm lint` (`eslint . --no-inline-config`) — exit **0**,
whole tree, no narrowing claimed.
- `pnpm check:nul-bytes` exit 0, plus a direct control-character scan
over all 8 changed paths — clean.
- Merged `origin/main` through `scripts/pm/os-regen-merge.sh` (its step
2 took main's side of `api-surface-declarations/ui.txt`, which both
sides moved, and step 3's hook held the regeneration debt until it was
discharged). This branch's delta against `origin/main` on that shard is
now **exactly the two `pagination` hunks**, with main's own advance
intact.
### The widening-tells reading, with its caveat
```
node scripts/pm/check-widening-tells.mjs --declaration yes --diff PRDIFF -> exit 0
✓ the claim declares `Clause-②: yes`, which this gate never blocks — a `yes` already
routes to contract review, so a tell on top of it decides nothing.
```
⚠️ **That exit 0 is the absence of a reading, not a clean one.** With
`yes` the gate short-circuits and examines **no file**. Run as a
**diagnostic only** with `--declaration no`, it exits 4 on two T1 tells:
`component.zod.ts:2689` (`pageSizeOptions`) and `:2692` (`pageSize`) —
*"a new key on a Zod object schema"*. Textually right, semantically
inverted for this diff: both members were already writable through
`z.unknown()`, which accepted everything; what the diff does is
**bound** them. That is a limitation of the matcher, not a signal about
this PR, and it is in the acceptance notes below rather than repaired
here.
## Acceptance notes
*The two paragraphs below were added by the `domain:spec#3` seat after
the body's single dev write, on the dev's own hand-over; ⛔ a dev writes
a PR body once, at creation.*
**The migration registry, with four open PRs adding entries to it.**
Mine, #19090, #19084 and #18319 each add one semantic entry. Identity
cannot collide silently: the entry id IS the identity and the **filename
is a function of it**, so a duplicate would be a loud git add/add
conflict — the generator says so in as many words, and the four ids are
four distinct files. Order is **derived** `(major, id)` from the
directory listing, with no index file and no positional consumer
(`migrations/chain.ts` keys by MAJOR, `MIGRATIONS_BY_MAJOR[m]`), so a
clean text merge cannot express a wrong *meaning* — the `18.` prefix is
the protocol-major bucket, not a sequence number. The gate is `pnpm
--filter @objectstack/spec check:migration-registry`, run at exit 0
(「229 semantic, 195 retired-key, 181 retired-def」 current): it proves
the emitted regions equal what the entries directory says, so a merge
that dropped one side reds and one that kept both out of order reds too.
Adjacency measured over the 141 existing `18.*` entries plus the four in
flight: **7 / 49 / 61** existing entries lie between mine and #19090 /
#19084 / #18319 — no pair is adjacent, and the register's own
insertion-only property then predicts a clean, current union whatever
the landing order. ⚠️ And `registry.ts` is deliberately **NOT** in the
`merge=os-regen` register (classified MIXED, 「a deferral would launder
the prose」), so a conflict there is **loud and a human's** — the
silent-drop class does not reach it.
**The hand-written docs negative, recorded so it is not reopened.**
Probe: hand-written `content/docs` trees (excluding `references/` and
`releases/`) authoring a `pageSize` value this narrowing refuses (`0`,
negative, decimal) → **ZERO**. **Lit control, same instrument:** it does
find authored `pageSize` occurrences —
`content/docs/api/data-api.mdx:42` (`?pageSize=5`) and
`content/docs/api/error-catalog.mdx:151` — over 2 hand-written pages and
9 pages including the generated tree, so the zero is a reading rather
than a dead grep. **Attribution, which is the part that matters:**
neither control hit is this door's `pagination.pageSize` —
`data-api.mdx` documents `pageSize` as an *unknown REST query parameter*
refused in favour of `top` / `$top` / `limit`, and the remaining pages
are the metadata response shape, the object page and the metadata-plugin
page. Four different `pageSize` members, none of them this one. ⇒
nothing owed on the hand-written side; the generated
`content/docs/references/ui/component.mdx` already moved in this diff.
The attribution step is the prescription of **#19093**, filed today
after a name-based hit produced a false stop-the-line alarm on a sibling
PR.
Observations found in passing. ⛔ None is filed as a card by this PR, and
none is in its scope.
- **The widening-tells matcher cannot tell a narrowing-inside-a-bag from
a widening.** A PR that honestly declares `Clause-②: no (narrowing)` — a
legal, precedented declaration
(`.changeset/17499-groupbyfield-non-padded.md` carries exactly it) — and
bounds a member inside a previously-`z.unknown()` bag is blocked at exit
4 by a T1 tell that names the bound as a widening, because the matcher
reads the added key text and not the member's prior schema. Reproduced
on this diff, above. The honest declaration is the blocked one. The
successor: the next accept-set narrowing on this board. Dedupe words:
`widening-tells T1 narrowing inside z.unknown bag`,
`check-widening-tells false tell narrowing`, `clause-2 no narrowing
blocked exit 4`.
- **`frozenColumns: z.number().optional()`** on this same door
(`component.zod.ts`) is unbounded, and the renderer reads it as a
leading-column count. ⛔ Not filed and ⛔ not touched: no repro, no
measured consumer breakage, and it is not this card's member. Noted, not
filed. The successor is any future PR on this door's numeric members.
- **`pagination: false` / `pagination: true` on `data-table` /
`object-data-table`** is authored in objectui and those props are not
declared in `ComponentPropsMap` at all, so nothing in this repo judges
them. Noted, not filed; that is the sibling repo's declaration surface,
not this door's.
## Notes for the reviewer
- ⛔ This PR does **not** hang, clear or touch `needs:contract-review`,
and writes **no label** — both carriers are the seat's write. `Clause-②:
yes` is here because triage ruled it; ⛔ this author does not review its
own clause-② verdict.
- `packages/spec/api-surface-declarations/ui.txt` moved because the
declaration text moved. PR #19024 removes all 17 of those shards; a
deletion-versus-modification conflict there resolves in favour of the
deletion and is expected — ⛔ not pre-solved here.
- No governed surface is in the diff (checked against
`GOVERNED_SURFACES` in `scripts/pm/check-governed-merges.mjs`):
`docs/audits/` is not `docs/adr/`.
- objectui#9853 is the consumer half's card and objectui#9896 its landed
repair; this is the declaration half and was never a prerequisite for
it. objectstack#18972 names this same class on the declaration side, and
#19083 landed its `scale` instance three commits before this branch's
merge base.
---
_Generated by [Claude Code](https://claude.ai/code)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent a675ad4 commit 9bb059d
8 files changed
Lines changed: 384 additions & 14 deletions
File tree
- .changeset
- content/docs/references/ui
- docs/audits
- packages/spec
- api-surface-declarations
- src
- migrations
- entries/semantic
- ui
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
514 | 514 | | |
515 | 515 | | |
516 | 516 | | |
517 | | - | |
518 | | - | |
| 517 | + | |
| 518 | + | |
519 | 519 | | |
520 | 520 | | |
521 | 521 | | |
| |||
Lines changed: 7 additions & 7 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
24 | | - | |
| 24 | + | |
25 | 25 | | |
26 | 26 | | |
27 | 27 | | |
| |||
44 | 44 | | |
45 | 45 | | |
46 | 46 | | |
47 | | - | |
| 47 | + | |
48 | 48 | | |
49 | 49 | | |
50 | 50 | | |
51 | 51 | | |
52 | | - | |
| 52 | + | |
53 | 53 | | |
54 | 54 | | |
55 | 55 | | |
| |||
66 | 66 | | |
67 | 67 | | |
68 | 68 | | |
69 | | - | |
| 69 | + | |
70 | 70 | | |
71 | 71 | | |
72 | 72 | | |
| |||
76 | 76 | | |
77 | 77 | | |
78 | 78 | | |
79 | | - | |
| 79 | + | |
80 | 80 | | |
81 | 81 | | |
82 | 82 | | |
| |||
155 | 155 | | |
156 | 156 | | |
157 | 157 | | |
158 | | - | |
| 158 | + | |
159 | 159 | | |
160 | 160 | | |
161 | 161 | | |
162 | 162 | | |
163 | 163 | | |
164 | 164 | | |
165 | 165 | | |
166 | | - | |
| 166 | + | |
167 | 167 | | |
168 | 168 | | |
169 | 169 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4470 | 4470 | | |
4471 | 4471 | | |
4472 | 4472 | | |
4473 | | - | |
| 4473 | + | |
| 4474 | + | |
| 4475 | + | |
| 4476 | + | |
4474 | 4477 | | |
4475 | 4478 | | |
4476 | 4479 | | |
| |||
11094 | 11097 | | |
11095 | 11098 | | |
11096 | 11099 | | |
11097 | | - | |
| 11100 | + | |
| 11101 | + | |
| 11102 | + | |
| 11103 | + | |
11098 | 11104 | | |
11099 | 11105 | | |
11100 | 11106 | | |
| |||
Lines changed: 43 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
12274 | 12274 | | |
12275 | 12275 | | |
12276 | 12276 | | |
| 12277 | + | |
| 12278 | + | |
| 12279 | + | |
| 12280 | + | |
| 12281 | + | |
| 12282 | + | |
| 12283 | + | |
| 12284 | + | |
| 12285 | + | |
| 12286 | + | |
| 12287 | + | |
| 12288 | + | |
| 12289 | + | |
| 12290 | + | |
| 12291 | + | |
| 12292 | + | |
| 12293 | + | |
| 12294 | + | |
| 12295 | + | |
| 12296 | + | |
| 12297 | + | |
| 12298 | + | |
| 12299 | + | |
| 12300 | + | |
| 12301 | + | |
| 12302 | + | |
| 12303 | + | |
| 12304 | + | |
| 12305 | + | |
| 12306 | + | |
| 12307 | + | |
| 12308 | + | |
| 12309 | + | |
| 12310 | + | |
| 12311 | + | |
| 12312 | + | |
| 12313 | + | |
| 12314 | + | |
| 12315 | + | |
12277 | 12316 | | |
12278 | 12317 | | |
12279 | 12318 | | |
| |||
0 commit comments