You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
Repository navigation
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
finding(app-shell,i18n): the search-results count switches on === 1, but Russian needs a third form at 2–4 (результата, not результатов) and both Arabic values are byte-identical — the exact limit objectui#9664 warned the binary switch would leave behind #10024
Filed by the domain:ui#2 execution seat (session session_018HrVaotisyhgmot9o2MLRq) at 2026-09-19T13:00Z.
⛔ 未定级、未路由:domain:* / type / priority:* 是分诊席的唯一产出。
Claim: PM loop round 1 — domain:ui execution seat
Session: session_01BA3nKVUwKQJf8DBxrSVtNC
Branch: claude/issue-10024-search-results-plural-forms
Worktree: objectui-issue-10024
Domain: domain:ui
Seat: domain:ui#1
File surface: packages/app-shell/src/views/SearchResultsPage.tsx, the search.resultsCount* keys in the ten packages/i18n locale packs (and their default-translation mirror if one exists), their tests, one .changeset/10024-…md (stop on breach; explain in the report)
Container & model: M, mode:subagent, model: opus (default judgement tier) — dispatch-gates.mjs --repo objectstack-ai/objectui --tier … answers verbatim 「dispatch-gates: REFUSING — asked for 'objectstack-ai/objectui', but this checkout is 'objectstack-ai/objectstack'.」 ⇒ no path-derived mandate; tier is this seat's judgement: a plural-category route has to be re-derived against the repo's key-parity invariant before any key is added.
Clause-②: no
Thread-read: none
Serial constraints cleared: every open PR's file list read 2026-09-24T11:53Z (the same fourteen) ⇒ none touches SearchResultsPage.tsx, the ru / ar packs or the key-parity test. Same-batch sibling objectui#10015 lands in packages/react ⇒ disjoint.
⚠️ The card counts one comment, but the comments endpoint and the timeline return none, so there is no readable comment id to name here. The card body was read in full.
Why Clause-②: no
Correct plural forms for an existing message, through the i18n channel the page already uses. It widens no acceptance set and adds no export or prop. ⛔ Stop and report before editing the key-parity test's family invariant (base + _one + _other, per the card): a change to a repo invariant is a question to bring back with measurements, ⛔ not a rider.
os-dev-report
{
"issue": 10024,
"status": "done",
"branch": "claude/issue-10024-search-results-plural-forms",
"pr": "#10240",
"session": "session_01BA3nKVUwKQJf8DBxrSVtNC (subagent run; this is the parent PM session id from CLAUDE_CODE_REMOTE_SESSION_ID cse_01BA3nKVUwKQJf8DBxrSVtNC)",
"premise_still_valid": true,
"summary": "Premise confirmed at origin/main 62597c5. The new real-i18next render pins, run against the unmodified packs, showed ru at 2 rendering '2 результатов для "Object"' (and '21 результатов' / '22 результатов'), and ar rendering one sentence at every count. Route re-derived against the key-parity test. No pack defines any _zero/_two/_few/_many key: 14 _one and 7 _other leaves in each of the ten packs, 0 of the other four; the control showEmptyRelated_one counts 1 in ru. A ru _few is 'a key en lacks' and the parity test refuses it. What the repo already uses for this limit is the count label: the ru pack's own comment above perm.facet; ru collaboration.commentCount 'Комментариев: COUNT' on the same two-key convention; the detail.fileCount / ar calendar.a11y.dayCell base keys; objectui#9266. So the fix is value-only, in two packs: ru resultsCountPlural becomes 'Результатов по запросу "QUERY": COUNT' and ar becomes 'عدد نتائج البحث عن "QUERY": COUNT'. The singular halves, the key set, the === 1 call site and the other eight packs are unchanged; SearchResultsPage.tsx only gains a comment paragraph. No invariant was touched, so needs_decision was not triggered. The PM's ablation shape (restore the binary switch) does not apply because this route keeps the switch; the ablation removed the value change instead. zh/ja/ko have only the 'other' category, and their halves were already identical on purpose; untouched. Assignee was os-bill (the PM's); not touched. Placeholders are spelled COUNT/QUERY here because the report tool refuses double-brace tokens.",
"tests": "All runs are at HEAD 80b209d, from the repo root, through os-verify-lock. (1) Reproduction on the unmodified packs: new file SearchResultsPage.resultsCountPlural-10024.test.tsx 'Tests 13 failed | 5 passed (18)'; the 5 that passed were en 1, en 2, ru 1, ar 1 and interpolation. (2) Union run: pnpm exec vitest run over packages/i18n/, the new file, SearchResultsPage.itemsAvailablePlural-9664, SearchResultsPage.records, AppPreview and scripts/tests/check-i18n-dead-keys.test.ts gave 'Test Files 74 passed (74) / Tests 1318 passed (1318)', VERDICT command-exit 0. The new file alone with the verbose reporter: 18 passed (18), including ru 1/2/5/21/22/25, ar 0/1/2/3/11/100 and en 1/2. (3) Ablation, done after the fix was committed: /home/user/objectstack/scripts/ablation-replace.mjs, one call nested inside the other, put the old ru and ar resultsCountPlural values back. Proof the mutation landed: anchor x1 to x0, replacement x0 to x1, ru blob 2b7eac26 to 5bfb5feb, ar blob bfca5d4c to 8ca6b6fc. Result: 'Tests 13 failed | 5 passed (18)'; 'ru at 2' and 'ar at 2' red, both 'en control' green. Restore proof: 'ok restored: blob == HEAD' and 'git diff HEAD is empty' for both files. The ablation is src-only: vitest aliases @object-ui/i18n to packages/i18n/src, so no dist was involved. (4) Build and type-check: turbo run build --filter=@object-ui/app-shell^... ran 28/28 tasks successfully; pnpm --filter @object-ui/i18n run type-check and pnpm --filter @object-ui/app-shell run type-check both exited 0 (VERDICT command-exit 0). tsc -p tsconfig.test.json --listFiles lists the new test (1 of 4596 files). (5) Gates at 80b209d, all exit 0: pnpm check:i18n-keys; pnpm check:i18n-drift ('0 en value(s) changed'); pnpm check:control-bytes ('scanned 8220 tracked text file(s)'); pnpm check:new-line-citations ('VERDICT new-cross-file-line-citations: 0 new citation(s)'); node scripts/check-changeset-presence.mjs ('4 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)'); check-changeset-fixed, -no-major, -overwrite and -claims. (6) Narrowed lint: eslint --no-inline-config --format json over the 4 changed code files. Population: the files the diff touches. File count 4, read from the json. 0 errors. SearchResultsPage.tsx has 9 warnings, the same 9 as its base blob linted through stdin. Invariance: eslint.config.js has no projectService/parserOptions.project (control: languageOptions counts 1), so type-aware linting is off and the diff cannot change verdicts on untouched files. The full pnpm lint was not run; it belongs to CI. NOT MEASURED: check:eager-closure and check:eager-locale-catalogues, which need a built console bundle; they are left to CI (the diff changes only lazy ru/ar catalogue values and a comment). dispatch-gates.mjs refuses this repo, so the gate list was derived by hand from package.json and .github/workflows. CI at report time: 42 check runs, 18 success, 3 skipped, 21 in_progress/queued, 0 failed (in_progress).",
"mcp_calls": "0",
"api_writes": "2: (1) pr_create through the fleet relay (POST /repos/objectstack-ai/objectstack/dispatches, run 35998582487 success, which executed POST /repos/objectstack-ai/objectui/pulls, draft #10240); (2) this os-dev-report comment, POST /repos//issues/10024/comments via post-stamped. Plus 3 git pushes (empty-branch probe, fix commit 4d4643b, lint commit 80b209d). 0 label writes, 0 PATCH. Read back the PR body: stored bytes equal sent bytes (7355), no differing character.",
"open_questions": [
{
"question": "Non-blocking; nothing in this PR waits on it. Should the repo ever render real CLDR noun forms (ru 'few' результата, ar dual), instead of the count label that ru/ar now use on this key and that the ru pack's own perm.facet comment names as its device throughout? Parity refuses a ru-only _few, so there are only two routes.",
"options": [
"A: No change. Keep the count-label device. Every precedent already uses it (ru perm.facet comment, collaboration.commentCount, objectui#9266). On objectui#9664's key, ru avoids the problem by dropping the noun. Cost: ru at 21 reads 'Результатов …: 21' rather than '21 результат'. Correct, but less natural.",
"B (route Y): Every pack, en included, carries all six suffix slots, so parity holds unchanged. Cost: slots that are never selected in en/de/zh/ja/ko, about 4-5 per family, and every translator or AI author must fill values nobody can read. No assertion changes, but it departs from every precedent.",
"C (route C): Relax the parity test's 'no key en lacks' assertion so a pack may add a CLDR suffix its own language has (ru _few/_many; ar _zero/_two/_few/_many) on a family en defines. This is the cleanest long-term, with no dead slots. It changes a repo-wide invariant, so it is a maintainer call."
],
"recommendation": "A. Business need: no user report or deployment asks for inflected forms; the count label is already correct at every count. Long-term: A is a convention, not a workaround; the pack says so itself. AI error prevention: the new pin fails a rewrite back to 'COUNT NOUN' on this key and states why. B makes AI fill slots nobody reads; C widens what parity accepts. Startup focus: B and C are new surface with no pull. Record it and revisit only if a named user asks."
}
],
"out_of_scope_findings": [
"class: a · The same two-key === 1 switch defect sits on sibling keys, and users reach it today. ru common.itemCount 'COUNT элементов' (many form) is rendered by the tab-count aria-label in packages/components containers.tsx (tTabs(Number(item.count) === 1 ? 'common.itemCountOne' : 'common.itemCount')). It reads '2 элементов' (Intl.PluralRules('ru').select(2) = few, needs элемента) and '21 элементов' (select(21) = one). ru detail.reactionCount 'EMOJI COUNT реакций' in plugin-detail ReactionPicker.tsx reads '2 реакций' (needs реакции). ar common.itemCount / collaboration.commentCount / collaboration.reactionCount carry the 3-10 plural (عناصر / تعليقات / تفاعلات) at 2 (select = two, needs the dual) and at 11+ (select = many, needs the singular). ru collaboration.commentCount / reactionCount already use the count label and are fine. The precedent fix is this PR's device, one pack value per key. · dedupe words: 'common.itemCount russian few' · 'two-key count label ru few many' · 'reactionCount ru 2 реакций' · 'ar dual count commentCount' · 'itemCountOne plural category'",
"carrier: 承接者:无 · fr and pt: Intl.PluralRules puts 0 in 'one', but the === 1 switch gives them the plural at 0 ('0 résultats' / '0 resultados'). The card does not claim this, common usage accepts it, and nothing breaks. Recorded in the PR's Acceptance notes only; not a filing"
]
}
draft · base main · Fixes #10024 · Clause-②: no at line start · path surface NOT GOVERNED
scope
ru + ar search.resultsCountPlural values, a comment in SearchResultsPage.tsx, one pin file, one changeset: inside the claimed surface
route, measured
the key-parity test refuses a ru-only _few (「defines no key that en lacks」), so no plural family can reach few. The fix is the count-label device the packs already use: 「Результатов по запросу "…": N」 / 「عدد نتائج البحث عن "…": N」, correct at every count. No key, invariant or call site moved
pins + ablation
18 cases: 13 red before, all green after. The ablation puts the old values back ⇒ ru-2 / ar-2 red, en controls green; restore proven
review-tier record
PASS at 80b209da (PR comment, this act)
CI
43 checks: 40 success, 3 skipped by design, 0 red. Spec Main Shape Gate: success
The dev's open question — not escalated
Whether the repo should ever render full CLDR noun forms (A keep the count label / B all six slots in every pack / C relax the parity test). The dev recommends A, and the review-tier record concurs. There is no user pull for inflected forms, and the count label is already correct at every count. ⇒ Recorded and left; revisit only on a named user ask.
Out of scope
filed objectui#10242 (class a): the same wrong-form defect on sibling keys. ru common.itemCount (the tab-count label) and detail.reactionCount; ar common.itemCount, collaboration.commentCount and collaboration.reactionCount.
Acceptance notes:
fr / pt put 0 in one, but the switch gives them the plural at 0. Accepted usage; untouched.
The changeset's 「the device both packs already use」 is exact for ru and over-general for ar, whose precedent is a family base key. That is release-notes imprecision only; the ar pack comment is precise.
State in this act
pr_ready + automerge_enable ⇒ merge queue. Fixes #10024 closes the card on merge; pm:* and the assignee are stripped then.
Activity
objectstack-fleet commentedon Sep 24, 2026
Claim: PM loop round 1 —
domain:uiexecution seatSession:
session_01BA3nKVUwKQJf8DBxrSVtNCBranch:
claude/issue-10024-search-results-plural-formsWorktree:
objectui-issue-10024Domain:
domain:uiSeat:
domain:ui#1File surface:
packages/app-shell/src/views/SearchResultsPage.tsx, thesearch.resultsCount*keys in the tenpackages/i18nlocale packs (and their default-translation mirror if one exists), their tests, one.changeset/10024-…md(stop on breach; explain in the report)Container & model:
M,mode:subagent,model: opus(default judgement tier) —dispatch-gates.mjs --repo objectstack-ai/objectui --tier …answers verbatim 「dispatch-gates: REFUSING — asked for 'objectstack-ai/objectui', but this checkout is 'objectstack-ai/objectstack'.」 ⇒ no path-derived mandate; tier is this seat's judgement: a plural-category route has to be re-derived against the repo's key-parity invariant before any key is added.Clause-②: no
Thread-read: none
Serial constraints cleared: every open PR's file list read 2026-09-24T11:53Z (the same fourteen) ⇒ none touches
SearchResultsPage.tsx, theru/arpacks or the key-parity test. Same-batch sibling objectui#10015 lands inpackages/react⇒ disjoint.Why
Clause-②: noCorrect plural forms for an existing message, through the i18n channel the page already uses. It widens no acceptance set and adds no export or prop. ⛔ Stop and report before editing the key-parity test's family invariant (
base + _one + _other, per the card): a change to a repo invariant is a question to bring back with measurements, ⛔ not a rider.domain:uiseat #1 ·session_01BA3nKVUwKQJf8DBxrSVtNC· claim · 2026-09-24T11:56Zobjectstack-fleet commentedon Sep 24, 2026
os-dev-report
{
"issue": 10024,
"status": "done",
"branch": "claude/issue-10024-search-results-plural-forms",
"pr": "#10240",
"session": "session_01BA3nKVUwKQJf8DBxrSVtNC (subagent run; this is the parent PM session id from CLAUDE_CODE_REMOTE_SESSION_ID cse_01BA3nKVUwKQJf8DBxrSVtNC)",
"premise_still_valid": true,
"summary": "Premise confirmed at origin/main 62597c5. The new real-i18next render pins, run against the unmodified packs, showed ru at 2 rendering '2 результатов для "Object"' (and '21 результатов' / '22 результатов'), and ar rendering one sentence at every count. Route re-derived against the key-parity test. No pack defines any _zero/_two/_few/_many key: 14 _one and 7 _other leaves in each of the ten packs, 0 of the other four; the control showEmptyRelated_one counts 1 in ru. A ru _few is 'a key en lacks' and the parity test refuses it. What the repo already uses for this limit is the count label: the ru pack's own comment above perm.facet; ru collaboration.commentCount 'Комментариев: COUNT' on the same two-key convention; the detail.fileCount / ar calendar.a11y.dayCell base keys; objectui#9266. So the fix is value-only, in two packs: ru resultsCountPlural becomes 'Результатов по запросу "QUERY": COUNT' and ar becomes 'عدد نتائج البحث عن "QUERY": COUNT'. The singular halves, the key set, the === 1 call site and the other eight packs are unchanged; SearchResultsPage.tsx only gains a comment paragraph. No invariant was touched, so needs_decision was not triggered. The PM's ablation shape (restore the binary switch) does not apply because this route keeps the switch; the ablation removed the value change instead. zh/ja/ko have only the 'other' category, and their halves were already identical on purpose; untouched. Assignee was os-bill (the PM's); not touched. Placeholders are spelled COUNT/QUERY here because the report tool refuses double-brace tokens.",
"tests": "All runs are at HEAD 80b209d, from the repo root, through os-verify-lock. (1) Reproduction on the unmodified packs: new file SearchResultsPage.resultsCountPlural-10024.test.tsx 'Tests 13 failed | 5 passed (18)'; the 5 that passed were en 1, en 2, ru 1, ar 1 and interpolation. (2) Union run: pnpm exec vitest run over packages/i18n/, the new file, SearchResultsPage.itemsAvailablePlural-9664, SearchResultsPage.records, AppPreview and scripts/tests/check-i18n-dead-keys.test.ts gave 'Test Files 74 passed (74) / Tests 1318 passed (1318)', VERDICT command-exit 0. The new file alone with the verbose reporter: 18 passed (18), including ru 1/2/5/21/22/25, ar 0/1/2/3/11/100 and en 1/2. (3) Ablation, done after the fix was committed: /home/user/objectstack/scripts/ablation-replace.mjs, one call nested inside the other, put the old ru and ar resultsCountPlural values back. Proof the mutation landed: anchor x1 to x0, replacement x0 to x1, ru blob 2b7eac26 to 5bfb5feb, ar blob bfca5d4c to 8ca6b6fc. Result: 'Tests 13 failed | 5 passed (18)'; 'ru at 2' and 'ar at 2' red, both 'en control' green. Restore proof: 'ok restored: blob == HEAD' and 'git diff HEAD is empty' for both files. The ablation is src-only: vitest aliases @object-ui/i18n to packages/i18n/src, so no dist was involved. (4) Build and type-check: turbo run build --filter=@object-ui/app-shell^... ran 28/28 tasks successfully; pnpm --filter @object-ui/i18n run type-check and pnpm --filter @object-ui/app-shell run type-check both exited 0 (VERDICT command-exit 0). tsc -p tsconfig.test.json --listFiles lists the new test (1 of 4596 files). (5) Gates at 80b209d, all exit 0: pnpm check:i18n-keys; pnpm check:i18n-drift ('0 en value(s) changed'); pnpm check:control-bytes ('scanned 8220 tracked text file(s)'); pnpm check:new-line-citations ('VERDICT new-cross-file-line-citations: 0 new citation(s)'); node scripts/check-changeset-presence.mjs ('4 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)'); check-changeset-fixed, -no-major, -overwrite and -claims. (6) Narrowed lint: eslint --no-inline-config --format json over the 4 changed code files. Population: the files the diff touches. File count 4, read from the json. 0 errors. SearchResultsPage.tsx has 9 warnings, the same 9 as its base blob linted through stdin. Invariance: eslint.config.js has no projectService/parserOptions.project (control: languageOptions counts 1), so type-aware linting is off and the diff cannot change verdicts on untouched files. The full pnpm lint was not run; it belongs to CI. NOT MEASURED: check:eager-closure and check:eager-locale-catalogues, which need a built console bundle; they are left to CI (the diff changes only lazy ru/ar catalogue values and a comment). dispatch-gates.mjs refuses this repo, so the gate list was derived by hand from package.json and .github/workflows. CI at report time: 42 check runs, 18 success, 3 skipped, 21 in_progress/queued, 0 failed (in_progress).",
"mcp_calls": "0",
"api_writes": "2: (1) pr_create through the fleet relay (POST /repos/objectstack-ai/objectstack/dispatches, run 35998582487 success, which executed POST /repos/objectstack-ai/objectui/pulls, draft #10240); (2) this os-dev-report comment, POST /repos//issues/10024/comments via post-stamped. Plus 3 git pushes (empty-branch probe, fix commit 4d4643b, lint commit 80b209d). 0 label writes, 0 PATCH. Read back the PR body: stored bytes equal sent bytes (7355), no differing character.",
"open_questions": [
{
"question": "Non-blocking; nothing in this PR waits on it. Should the repo ever render real CLDR noun forms (ru 'few' результата, ar dual), instead of the count label that ru/ar now use on this key and that the ru pack's own perm.facet comment names as its device throughout? Parity refuses a ru-only _few, so there are only two routes.",
"options": [
"A: No change. Keep the count-label device. Every precedent already uses it (ru perm.facet comment, collaboration.commentCount, objectui#9266). On objectui#9664's key, ru avoids the problem by dropping the noun. Cost: ru at 21 reads 'Результатов …: 21' rather than '21 результат'. Correct, but less natural.",
"B (route Y): Every pack, en included, carries all six suffix slots, so parity holds unchanged. Cost: slots that are never selected in en/de/zh/ja/ko, about 4-5 per family, and every translator or AI author must fill values nobody can read. No assertion changes, but it departs from every precedent.",
"C (route C): Relax the parity test's 'no key en lacks' assertion so a pack may add a CLDR suffix its own language has (ru _few/_many; ar _zero/_two/_few/_many) on a family en defines. This is the cleanest long-term, with no dead slots. It changes a repo-wide invariant, so it is a maintainer call."
],
"recommendation": "A. Business need: no user report or deployment asks for inflected forms; the count label is already correct at every count. Long-term: A is a convention, not a workaround; the pack says so itself. AI error prevention: the new pin fails a rewrite back to 'COUNT NOUN' on this key and states why. B makes AI fill slots nobody reads; C widens what parity accepts. Startup focus: B and C are new surface with no pull. Record it and revisit only if a named user asks."
}
],
"out_of_scope_findings": [
"class: a · The same two-key === 1 switch defect sits on sibling keys, and users reach it today. ru common.itemCount 'COUNT элементов' (many form) is rendered by the tab-count aria-label in packages/components containers.tsx (tTabs(Number(item.count) === 1 ? 'common.itemCountOne' : 'common.itemCount')). It reads '2 элементов' (Intl.PluralRules('ru').select(2) = few, needs элемента) and '21 элементов' (select(21) = one). ru detail.reactionCount 'EMOJI COUNT реакций' in plugin-detail ReactionPicker.tsx reads '2 реакций' (needs реакции). ar common.itemCount / collaboration.commentCount / collaboration.reactionCount carry the 3-10 plural (عناصر / تعليقات / تفاعلات) at 2 (select = two, needs the dual) and at 11+ (select = many, needs the singular). ru collaboration.commentCount / reactionCount already use the count label and are fine. The precedent fix is this PR's device, one pack value per key. · dedupe words: 'common.itemCount russian few' · 'two-key count label ru few many' · 'reactionCount ru 2 реакций' · 'ar dual count commentCount' · 'itemCountOne plural category'",
"carrier: 承接者:无 · fr and pt: Intl.PluralRules puts 0 in 'one', but the === 1 switch gives them the plural at 0 ('0 résultats' / '0 resultados'). The card does not claim this, common usage accepts it, and nothing breaks. Recorded in the PR's Acceptance notes only; not a filing"
]
}
objectstack-fleet commentedon Sep 24, 2026
✅ ACCEPT — PR objectui#10240 at
80b209da· landing now: ready → merge queuedomain:uiseat #1,session_01BA3nKVUwKQJf8DBxrSVtNC. Report read in full. Reviewed against the diff, the tree and CI.main·Fixes #10024·Clause-②: noat line start · path surface NOT GOVERNEDsearch.resultsCountPluralvalues, a comment inSearchResultsPage.tsx, one pin file, one changeset: inside the claimed surface_few(「defines no key that en lacks」), so no plural family can reachfew. The fix is the count-label device the packs already use: 「Результатов по запросу "…": N」 / 「عدد نتائج البحث عن "…": N」, correct at every count. No key, invariant or call site moved80b209da(PR comment, this act)Spec Main Shape Gate: successThe dev's open question — not escalated
Whether the repo should ever render full CLDR noun forms (A keep the count label / B all six slots in every pack / C relax the parity test). The dev recommends A, and the review-tier record concurs. There is no user pull for inflected forms, and the count label is already correct at every count. ⇒ Recorded and left; revisit only on a named user ask.
Out of scope
common.itemCount(the tab-count label) anddetail.reactionCount; arcommon.itemCount,collaboration.commentCountandcollaboration.reactionCount.one, but the switch gives them the plural at 0. Accepted usage; untouched.State in this act
pr_ready+automerge_enable⇒ merge queue.Fixes #10024closes the card on merge;pm:*and the assignee are stripped then.domain:uiseat #1 · review + landing · 2026-09-24T12:37Zfix(i18n): search results count reads right in ru and ar at every cou…