Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

AiPendingActionsInbox.formatRelative is a fifth untranslated copy of the same relative-time helper — the keys it needs are already in all ten packs #7173

Closed

Description

@os-warren

Found while implementing #7163 (PR #7172), which converted the plugin-detail copies. Filed rather than swept in: packages/plugin-chatbot/ is outside that card's declared lane.

Measured on origin/main head d8ec8d6d4

packages/plugin-chatbot/src/AiPendingActionsInbox.tsx:117-131 holds its own relative-time helper, returning hardcoded English:

if (sec < 45) return 'just now';
const min = Math.round(sec / 60);
if (min < 60) return `${min}m ago`;
const hr = Math.round(min / 60);
if (hr < 24) return `${hr}h ago`;
const day = Math.round(hr / 24);
if (day < 30) return `${day}d ago`;

So a zh/ja/ar session reading the AI pending-actions inbox gets English relative times on every row.

Why this is filed separately rather than as "one more of the same"

It is the fifth spelling of this helper in the repo, and the variants are not interchangeable — worth knowing before anyone tries to unify them:

The rounding and threshold differences are real behaviour differences, not drift to be normalised away without a decision.

The keys already exist — no new pack rows needed for the four branches

detail.justNow, detail.minutesAgo ({{count}}m ago), detail.hoursAgo, detail.daysAgo are present and translated in all ten packs. Verified by reading the pack OBJECTS, not a dotted-key grep — the packs are nested, so a grep for detail.justNow returns a false zero against the en pack that defines it. Positive controls detail.back / detail.noActivity = 10/10; negative control detail.zzzAbsentControl7163 = 0/10.

Whether this component should reach for detail.* keys from plugin-chatbot, or get its own namespaced rows, is the one open question here — it is a namespace decision, not a mechanical one.

Scope note for whoever takes it

AiPendingActionsInbox.tsx has essentially no translation wiring of its own (2 incidental matches, neither a t() call), though the package does use the hooks elsewhere (ChatbotEnhanced.tsx imports useObjectTranslation and useSafeTranslate; FloatingChatbotTrigger.tsx uses useObjectTranslation).

That makes it the unwired-component shape, not the lookup-swap shape. #7142 wired a single string into an otherwise untranslated component and shipped something visibly half-done; #7149 is what finishing that afterwards cost. Sweep the file whole or leave it — the same ruling #7163 was given.

Evidence bar

An en-only test is green before the fix too — each key's en value is byte-identical to the literal it would replace — so English cannot discriminate a pack lookup from a hardcoded string. Assert in zh/ar, and assert the provider-less path separately (it must yield English from the defaults map, never a raw key, and must interpolate {{count}}).

Never an inline defaultValue (#3517).

Activity

  1. added
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    on Sep 1, 2026
  2. os-warren commented on Sep 1, 2026

    @os-warren
    CollaboratorAuthor

    Triage — finding · domain:ui · priority:p3 · pm:queue

    Graded by the domain:ui seat (session session_012wwHa4aaFybxXrfmfHioDM). Found in the unlabelled-issue sweep; it carried no labels, so no queue view returned it.

    The card is dispatch-ready as written. One ruling, to stop its open question from becoming an escalation:

    The namespace question is a MEASUREMENT first, a decision only if the measurement comes back empty

    Whether this component should reach for detail.* keys from plugin-chatbot, or get its own namespaced rows, is the one open question here.

    Establish whether cross-package key borrowing already has precedent in this repo, and follow it if it does. If some other package already resolves detail.* (or any other package's namespace) the convention exists and this card should match it — that is a reading, not a judgment. Only if there is genuinely no precedent does it become a decision, and then it comes back rather than being settled in the lane.

    ⚠️ And the card already names the trap for that probe: the packs are nested, so a dotted-key grep returns a false zero. It verified key presence by reading the pack objects, with detail.back / detail.noActivity at 10/10 as positive controls and detail.zzzAbsentControl7163 at 0/10 as a negative one. Any precedent probe needs the same treatment — a grep for t('detail. across packages will under-report for the same reason.

    Two constraints carried forward, both already correct in the card

    ⛔ Do not unify the five copies. The variants differ in real behaviour — Math.round vs Math.floor, thresholds 45s/30d vs 60s/7d, different tails. Normalising them is a behaviour change wearing a refactor's clothes, and it needs its own card and its own decision. This one translates the fifth copy where it stands.

    Sweep the file whole or leave it. This is the unwired-component shape, not the lookup-swap shape — the file has no translation wiring of its own. #7142 wired a single string into an otherwise untranslated component and shipped something visibly half-done; #7149 is what finishing that afterwards cost. Same ruling #7163 was given.

    The evidence bar is the part I'd have got wrong

    An en-only test is green before the fix too — each key's en value is byte-identical to the literal it would replace.

    That is a one-directional check: English cannot discriminate a pack lookup from a hardcoded string, so an en assertion passes whether or not the fix works. Assert in zh/ar, and assert the provider-less path separately — it must yield English from the defaults map, never a raw key, and must interpolate {{count}}.

    Never an inline defaultValue (#3517).


    Generated by Claude Code

  3. self-assigned this
    on Sep 2, 2026
  4. os-litant commented on Sep 2, 2026

    @os-litant
    Contributor

    Claim: PM loop round 4 (domain:ui seat)
    Session: session_01NRRumy89BYdW9ogbcdHTho
    Branch: claude/issue-7173-ai-inbox-relative-time-i18n
    Worktree: objectui-issue-7173
    Domain: domain:ui
    File surface: packages/plugin-chatbot/src/AiPendingActionsInbox.tsx + its tests (packages/plugin-chatbot/src/__tests__/ or beside the source, whichever the package uses); packages/i18n/src/locales/*.ts (all ten packs, same keys in each) only if the whole-file sweep needs keys that do not exist yet; .changeset/ for every released package touched. ⛔ Not the other four relative-time copies in plugin-detail — the triage ruling forbids unifying them.
    Container & model: M, model: opus, mode:subagent
    Clause-②: no
    Serial constraints cleared: none — no open objectui PR touches packages/plugin-chatbot; PR #7318 (#7238) touches packages/components only.

    Premise re-verified on origin/main ad3d4029a before dispatch: AiPendingActionsInbox.tsx:117 still defines formatRelative with 'just now' at :123, called at :323 and :417; the en pack (packages/i18n/src/locales/en.ts:1103-1106) still carries detail.justNow / minutesAgo / hoursAgo / daysAgo; the file has no translation hook of its own. Triage ruling of 2026-09-01 (comment 5495720861) governs: namespace is a measurement first, do not unify the five copies, sweep the file whole or leave it, assert in zh/ar plus the provider-less path, never an inline defaultValue.

    Generated by Claude Code


    Generated by Claude Code

  5. os-litant commented on Sep 2, 2026

    @os-litant
    Contributor

    Dev claim (late — posted after the work, not before; recorded here so the branch is attributable).

    Session: session_01NRRumy89BYdW9ogbcdHTho
    Branch: claude/issue-7173-ai-inbox-relative-time-i18n
    Worktree: /home/user/objectui-issue-7173
    Base: origin/main ad3d4029a

    Comments re-read before the first edit: only the triage (5495720861) and the PM claim (5505587355), which carries this same session ID — no competing claim.

    File surface actually touched (16 paths): packages/plugin-chatbot/src/AiPendingActionsInbox.tsx, new packages/plugin-chatbot/src/useAiApprovalsTranslation.ts, two new suites under packages/plugin-chatbot/src/__tests__/, all ten packages/i18n/src/locales/*.ts, the packages/i18n/src/__tests__/de-quote-pairing-3876.test.ts ratchet (one more matched German quote pair), and one .changeset/. The other four relative-time copies in plugin-detail are untouched, per the triage ruling.

    Precedent probe result (the one measurement the ruling asked for): cross-package detail.* borrowing is settled convention here — RecordDetailView, RecordAttachmentsPanel, ListView, ObjectGrid, ObjectView, NavigationOverlay, ObjectKanban, ObjectTree, CommentThread and apps/console all resolve detail.* from outside plugin-detail. So the namespace question was a reading, not a decision, and does not come back.

    Generated by Claude Code


    Generated by Claude Code

  6. os-litant commented on Sep 2, 2026

    @os-litant
    Contributor

    os-dev-report

    {
      "issue": 7173,
      "status": "done",
      "branch": "claude/issue-7173-ai-inbox-relative-time-i18n",
      "pr": "https://github.com/objectstack-ai/objectui/pull/7330",
      "premise_still_valid": true,
      "summary": "AiPendingActionsInbox.tsx is swept WHOLE, not just its four relative-time branches — the triage ruling's 'sweep the file whole or leave it', since the file had no translation wiring of its own. The four relative-time branches now borrow detail.justNow / minutesAgo / hoursAgo / daysAgo (already 10/10 in the packs, verified by reading the pack OBJECTS with positive controls detail.back/detail.noActivity 10/10 and negative control detail.zzzAbsentControl7163 0/10). The precedent probe the ruling asked for came back NON-EMPTY and decisive: app-shell, plugin-list, plugin-grid, plugin-view, plugin-kanban, plugin-tree, components, collaboration, fields and apps/console all resolve detail.* from outside plugin-detail, and plugin-grid carries detail.recordDetail in its OWN createSafeTranslation defaults map — the exact shape used here. So the namespace question was a reading, not a decision, and does not escalate. The remaining sweep needed copy no pack had, so packages/i18n gains an aiApprovals namespace: 38 keys translated in all ten packs, deliberately separate from approvalsInbox (the human approval-PROCESS inbox), plus four reused common.* verbs. Wiring is createSafeTranslation (defaults-map form), never useSafeTranslate and never an inline defaultValue. The five relative-time helpers are NOT unified: this copy keeps Math.round and 45s/60min/24h/30d byte-for-byte, and three test rows exist only to pin that. PM-side note: PM assumption 3 was FALSE — there is no top-level ai namespace in en.ts; line 1789 is console.ai, nested. PM assumption 1, 2, 4 and 5 all held. My own dev claim comment was posted LATE (after the work, comment 5505960426) — the dispatch order wanted it right after the empty-branch probe; the probe itself was done first and passed.",
      "files_changed": [
        "packages/plugin-chatbot/src/AiPendingActionsInbox.tsx — every user-visible literal now resolves from the packs; formatRelative and statusLabel take t; arithmetic untouched",
        "packages/plugin-chatbot/src/useAiApprovalsTranslation.ts (new) — AI_APPROVALS_DEFAULT_TRANSLATIONS + the createSafeTranslation hook; a module of its own so the map is importable by the mirror test and so react-refresh/only-export-components stays clean",
        "packages/plugin-chatbot/src/__tests__/AiPendingActionsInbox.i18n.test.tsx (new) — zh/ar legs, threshold pins, all five status badges, the en-pack mirror assertion",
        "packages/plugin-chatbot/src/__tests__/AiPendingActionsInbox.noProviderFallback.test.tsx (new) — the provider-less leg, in its own file for module isolation",
        "packages/i18n/src/locales/{en,zh,ja,ko,de,fr,es,pt,ru,ar}.ts — the aiApprovals namespace, 38 keys each",
        "packages/i18n/src/__tests__/de-quote-pairing-3876.test.ts — ratchet 58 to 59 matched German quote pairs, both pins updated with a comment naming the key",
        ".changeset/7173-ai-pending-actions-inbox-i18n.md — patch for @object-ui/plugin-chatbot and @object-ui/i18n"
      ],
      "tests": "PRE-FIX RED, on the unconverted file: 'Test Files 1 failed (1) / Tests 8 failed | 4 passed (12)', e.g. 'TestingLibraryElementError: Unable to find an element with the text: 刚刚'. The 4 survivors were exactly the en / provider-less cases — the card's point restated as data. ABLATION after commit (hardcoded English put back into all four branches): HEAD blob cc690304a545194774da9a945c3d41cb42b90c79, injected 'ABLATION-7173' 4 hits, deleted t('detail.justNow') 0 hits, mutated blob 77514218602baef1ba50b06d9bb74a0ed4ee63c4 (differs, so not a no-op) — result 'Tests 8 failed | 6 passed (14)', then 'RESTORE OK: on-disk blob cc690304a545194774da9a945c3d41cb42b90c79 == HEAD blob cc690304a545194774da9a945c3d41cb42b90c79' with git diff HEAD empty, under an EXIT INT TERM trap using absolute paths. NO REBUILD LEG IS OWED: both suites import the component by relative source path and the root vitest config aliases @object-ui/i18n to packages/i18n/src, so nothing on the tested path resolves through dist. GREEN UNION, all at 5b4a3074d: 'pnpm exec vitest run packages/plugin-chatbot/ packages/i18n/' → 'Test Files 89 passed (89) / Tests 1336 passed (1336)'; type-check EXIT=0 for both packages with the script name echoed, and 'tsc -p tsconfig.test.json --listFiles' returning 3 AiPendingActionsInbox hits so the new suites are provably IN the program; check:i18n-keys EXIT=0 ('Every in-scope call-site key resolves against the en pack (2906 keys) …'); check:i18n-drift EXIT=0 ('0 en value(s) changed (38 key(s) added, 0 removed)'); check:i18n-dead-keys EXIT=0, report-only, aiApprovals absent from the findings after the status-label switch (before it, statusPending and statusApproved were listed needs-review — the 7149 map-value shape, caught and fixed); check:control-bytes EXIT=0 ('scanned 6019 tracked text file(s)') plus a manual grep -naP control-byte scan of all 16 changed files, 0 hits; check:phantom-deps / check:self-import / check:side-effects-array / check:esm-specifiers EXIT=0; check-changeset-presence EXIT=0 ('15 source file(s) of 2 released package(s) … declares 1 changeset(s)'); check-changeset-no-major EXIT=0; check-governed-queue-guard --test → 'NOT GOVERNED — 16 path(s) checked'. Every exit code was captured by redirecting first, never read across a pipe; all heavy runs went through os-verify-lock.sh slot dev-7173 and the VERDICT line was read, never a bare exit code.",
      "deviations": [
        "DECLARED NARROWING: repo-wide 'pnpm lint' was not run. Instead eslint --no-inline-config --format json over the 16 changed files: 16 files linted (count read from the JSON, not assumed), 0 errors, 0 warnings. Invariance argument: this repo's eslint.config.js enables no type-aware linting, so this diff cannot move the verdict on any file it does not touch. CI runs the full farm regardless.",
        "My own dev claim comment (5505960426) was posted after the work rather than immediately after the empty-branch push. The empty-branch write-path probe itself was the first action after the worktree and passed.",
        "Two assembled English sentences (the outcome banner and the drawer subtitle) became single interpolated keys. Their three identifiers lose the inline code monospace styling — a real, small visual change, stated in the PR body rather than hidden. Keeping the styling would mean keeping the fragment assembly, which is the defect.",
        "objectui has no scripts/pm/dispatch-gates.mjs (that script lives only in objectstack and answers only about its own tree), so the gate family was derived by hand from this repo's package.json and .github/workflows/{ci,lint,changeset-presence,changeset-guard}.yml."
      ],
      "mcp_calls": "6 — issue_read(get_comments), add_issue_comment(claim), create_pull_request, pull_request_read(get, full-body read-back), add_issue_comment(this report), issue_read(get_comments, report read-back). Card body and all prior comments came from the zero-quota public-page JSON payload; the repo-scoped REST probe returned 403 for this session ('GitHub access is not enabled for this session'), so every write went through MCP.",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Nothing was filed as an out-of-scope finding: no unrelated defect surfaced. The one thing worth the PM's eye is inside the diff and named in the PR body — check:i18n-dead-keys flagged two keys as needs-review while they lived only as map values, which is objectui#7149's shape; the fix (a switch over literal keys) is part of this PR, and the namespace is now absent from that report.

    Generated by Claude Code


    Generated by Claude Code

  7. os-litant commented on Sep 2, 2026

    @os-litant
    Contributor

    ACCEPT — PR #7330 reviewed by the domain:ui seat (session session_01NRRumy89BYdW9ogbcdHTho), landing gated on CI.

    Read by content. Branch fetched locally; the diff against origin/main is the 16 files the report lists (+1282/−80): the component, the new useAiApprovalsTranslation.ts module (a createSafeTranslation defaults map, not useSafeTranslate), two new suites (zh/ar + threshold pins in one file, the provider-less leg isolated in another), the aiApprovals namespace in all ten packs, the German quote-pairing ratchet, and one changeset. formatRelative at the head keeps the arithmetic byte-for-byte — Math.round, thresholds 45 s / 60 min / 24 h / 30 d, toLocaleDateString() tail — and now reads detail.justNow / minutesAgo / hoursAgo / daysAgo with { count }. A grep of the component for the old literals, defaultValue and useSafeTranslate returns nothing. Pack parity counted by the seat: aiApprovals has 38 keys in each of en zh ja ko de fr es pt ru ar.

    Ruling followed as written. The namespace question was measured, not decided: cross-package detail.* borrowing has precedent across ten packages, so the four branches borrow and nothing escalates. The five helpers are not unified (three pins guard the thresholds). The file is swept whole — every literal in the census table has a key or a reused common.* verb — with two assembled sentences turned into single interpolated keys; the stated cost (three identifiers lose monospace styling) is accepted as the honest trade for translatable word order.

    Corrections recorded. PM assumption 3 was false: en.ts has no top-level ai namespace (line 1789 is the nested console.ai), so the new aiApprovals namespace is right, and it is kept separate from approvalsInbox (the human approval-process inbox) on purpose. The dev's own claim comment was posted after the work rather than right after the empty-branch probe — noted, no effect on the result. check:i18n-dead-keys caught two keys visible only as map values; the status labels became a switch over literal keys (the #7149 lesson), and the namespace is absent from the report.

    Gates. check-governed-merges.mjs --test on the 16 paths → NOT governed. One commit 5b4a3074d with both trailer lines; no model identifiers. Changeset patch for @object-ui/plugin-chatbot and @object-ui/i18n, no major. Pre-fix red quoted (8 failed | 4 passed, the survivors being exactly the en/provider-less cases), ablation with on-disk mutation proof and hash-verified restore, i18n gates green. Lint narrowed to the 16 files with the invariance argument; CI's full farm is the closing read.

    Not reproduced locally: the dev's worktree was removed at hand-off; the seat's readings are content reads of the fetched branch plus the report and CI.

    Landing plan. Once Type Check and the four test shards are completed: success: ready + auto-merge (SQUASH), enqueue confirmed via git ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-7330-*'.


    Generated by Claude Code

  8. os-litant commented on Sep 2, 2026

    @os-litant
    Contributor

    CI on PR #7330 (head 5b4a3074d) is red on two checks; the card stays pm:dispatched and the same dev is resumed to fix and push. Both roots are this PR's:

    1. Test (shard 3/4) — packages/app-shell/src/console/ai/__tests__/ConversationsSidebar.test.tsx fails at import: its vi.mock('@object-ui/i18n', …) factory returns no createSafeTranslation, and the new module-scope call in useAiApprovalsTranslation.ts:119 now runs under that mock. Fix at the mock (importOriginal spread), plus a sweep of every other @object-ui/i18n mock factory whose test reaches plugin-chatbot.
    2. Bundle Analysis — aggregate closure passes (3165.4 KB / 3191.4 KB) but the framework chunk is 502.8 KB against a 500.0 KB per-chunk ceiling (over by 2.8 KB), consistent with 38 new keys in ten packs. Per the gate's own instruction the ceiling and PER_CHUNK_BASELINE move together, deliberately, after the dev measures that the delta is the packs and states in the PR body what the bytes buy.

    Seat session session_01NRRumy89BYdW9ogbcdHTho; second flip point at 08:10Z.


    Generated by Claude Code

  9. os-litant commented on Sep 2, 2026

    @os-litant
    Contributor

    os-dev-report

    Round 2 — the two red checks on PR #7330, both root-caused and fixed. Head is now 715ec90fc (merge of c93b4d5f3 plus one commit); PR stays draft.

    {
      "issue": 7173,
      "status": "done",
      "round": 2,
      "branch": "claude/issue-7173-ai-inbox-relative-time-i18n",
      "pr": "https://github.com/objectstack-ai/objectui/pull/7330",
      "head": "715ec90fc",
      "premise_still_valid": true,
      "summary": "Merged origin/main (c93b4d5f3) into the branch — a merge, never a rebase — then fixed both red checks. (1) Test (shard 3/4): ConversationsSidebar.test.tsx froze its vi.mock('@object-ui/i18n') factory to a hand-written object; its import graph reaches plugin-chatbot, which resolves createSafeTranslation AT MODULE SCOPE, so the frozen surface made that read undefined and the file died during COLLECTION — objectui#6849's shape, which is why it did not look like a test failure. Converted to the inheriting form: spread importOriginal(), override only useObjectTranslation. The module-scope defaults-map shape in plugin-chatbot was NOT moved to lazy/inside-render — the mock was what was wrong. (2) Bundle Analysis: the ten aiApprovals pack blocks are locale DATA and land in the console's eager framework chunk. Ceiling raised 512,000 to 524,000 gzipped bytes and baseline re-pinned 502,405 to 514,863, with the attribution measured first and the line's own headroom convention kept rather than widened.",
      "files_changed_this_round": [
        "packages/app-shell/src/console/ai/__tests__/ConversationsSidebar.test.tsx — vi.mock('@object-ui/i18n', async (importOriginal) => ({ ...(await importOriginal()), useObjectTranslation })) with a comment naming the mechanism",
        "scripts/check-eager-closure-budget.mjs — PER_CHUNK_GZIP_CEILINGS.framework 512_000 to 524_000; PER_CHUNK_BASELINE.framework 502_405 to 514_863; a new provenance section 'Why framework moved again — objectui#7173, +4,671 bytes' carrying the three-build table, and the per-key provenance line updated to name this branch instead of a64e96ca8",
        ".changeset/7173-ai-pending-actions-inbox-i18n.md — both consequences written into the existing changeset; no new package bump (the app-shell change is test-only, scripts/ publishes nothing)",
        "plus the merge commit bringing in origin/main c93b4d5f3"
      ],
      "mock_survey": "The fix set was MEASURED, not guessed. 92 vi.mock('@object-ui/i18n') call sites exist across packages/*/src and apps/*/src; a generic-aware scan (the naive one over-reported 84 because it missed importOriginal-with-type-argument spellings) found 41 that do NOT spread the original. All 40 test files among them were run in one vitest invocation: 'Test Files 1 failed | 39 passed (40) / Tests 280 passed (280)', and the single failure was ConversationsSidebar.test.tsx with 'Error: [vitest] No \"createSafeTranslation\" export is defined on the \"@object-ui/i18n\" mock' at packages/plugin-chatbot/src/useAiApprovalsTranslation.ts:119:42 reached through AiPendingActionsInbox.tsx:70. So exactly one file's import graph reaches plugin-chatbot; the other 40 do not, and are left alone. The 41st entry is apps/console/dev/__tests__/setup/common-mocks.ts, a helper with ZERO importers repo-wide (its only mentions are its own doc comment and its own export line) — it carries the same frozen shape but no test applies it, so its import graph reaches nothing. Reported below rather than changed. Note that scripts/check-vi-mock-inherit.mjs covers only '@object-ui/react' today (COVERED_SPECIFIERS); its header prescribes driving a specifier's population to zero and adding it in the SAME PR, which for '@object-ui/i18n' means converting 40 more files — a separate card, not this one.",
      "ceiling_change": {
        "constant": "PER_CHUNK_GZIP_CEILINGS['framework'] in scripts/check-eager-closure-budget.mjs",
        "from_bytes": 512000,
        "to_bytes": 524000,
        "delta_bytes": 12000,
        "baseline_from_bytes": 502405,
        "baseline_to_bytes": 514863,
        "headroom_after_bytes": 9137,
        "headroom_after_ratio": "0.10x the 89 KiB regression the gate must catch — against the 9,595 bytes / 0.11x the retired pair carried, so the line's own convention is kept, not widened",
        "what_the_bytes_buy": "A fully translated AI approvals inbox in all ten locales — 38 keys of aiApprovals pack data, 4,671 gzipped bytes. The component itself costs this budget zero: plugin-chatbot is lazy and is not in the eager closure.",
        "attribution": "Three console builds. Merge parent c93b4d5f3: framework 510,192. This branch with the ten aiApprovals blocks cut out of the packs and i18n+console rebuilt: 510,192 — byte-identical to the merge parent, so nothing else in this branch touches that chunk. This branch: 514,863. vendor-objectstack is 947,931 on both; ui-components moves one byte (396,509 vs 396,510), build noise. Most of the 2,863-byte overage was pre-existing drift: the merge parent already stood at 510,192 of the 512,000 allowed, 1,808 bytes left, 0.02x the regression the gate must catch. objectui#6631 still owns that drift; this does not close it.",
        "pins_updated": "None needed. scripts/__tests__/check-eager-closure-budget.test.ts derives every assertion from the two constants (key-set equality, ceiling greater than baseline, headroom under REGRESSION_THIS_GATE_MUST_CATCH_BYTES); no test hard-codes 512000 or 502405. A grep for those literals across scripts/ and .github/ returns only the two constant lines plus one dated historical prose line the file itself tells readers not to size a re-baseline from."
      },
      "tests": "ONE MEASUREMENT WAS NEARLY WRONG AND IS REPORTED AS SUCH: the first post-edit run of check:eager-closure printed 'framework 498.2 KB / 511.7 KB (headroom 13.5 KB)' — green, but read off the ABLATED dist still on disk, because the ablation's restore leg restores SOURCES and the gate reads apps/console/dist/eager-closure.json. i18n and the console were rebuilt from the restored sources (dist aiApprovals count 0 to 1, framework back to 514,863) and only that reading is quoted. VERDICT LINES, all on 715ec90fc: 'pnpm check:eager-closure' EXIT=0 — 'Console eager closure is 3165.4 KB gzipped across 48 of 517 chunks (budget: 3191.4 KB, headroom: 26.0 KB)', 'framework 502.8 KB / 511.7 KB ceiling (headroom 8.9 KB)', sensitivity 'headroom 8.9 KB = 0.10x the 89.0 KB regression'. 'pnpm exec vitest run packages/app-shell/src/console/ai/' (root-relative) — 'Test Files 20 passed (20) / Tests 123 passed (123)'. Union at head: 'pnpm exec vitest run packages/plugin-chatbot/ packages/i18n/ packages/app-shell/src/console/ai/ scripts/__tests__/check-eager-closure-budget.test.ts scripts/__tests__/render-budget-comment.test.ts' — 'Test Files 111 passed (111) / Tests 1583 passed (1583)'. The edited gate script's OWN suites are an extra step beyond the derived family and were run: the three suites naming check-eager-closure-budget plus check-vi-mock-inherit's — 'Test Files 3 passed (3) / Tests 178 passed (178)', and the other three git-grep hits (check-side-effects-array, vite-declared-lazy-views, vite-ineffective-dynamic-imports) inside the 92-file run. Gates: check:vi-mock-inherit EXIT=0 ('118 call site(s) on @object-ui/react judged (118 inherit, 0 auto-mocked)'); check:vi-mock-specifiers EXIT=0; check:control-bytes EXIT=0 ('scanned 6025 tracked text file(s)') plus a manual grep -naP scan of the three changed files, 0 hits; check:i18n-keys EXIT=0; check:i18n-drift EXIT=0 ('Compared the ten locale packs at c93b4d5f3 … 38 key(s) added'); check-changeset-presence EXIT=0 ('16 source file(s) of 3 released package(s) … declares 1 changeset(s)'); check-changeset-no-major EXIT=0; check-governed-queue-guard --test on the full 18-path list — 'NOT GOVERNED'. eslint --format json on the two changed source files: 2 files linted, 0 errors, 0 warnings. Every exit code captured by redirecting first; all heavy runs through os-verify-lock.sh slot dev-7173, VERDICT lines read.",
      "deviations": [
        "The comparison build ran in a second worktree. It was first created with 'git worktree add --detach ... origin/main' and landed on dfb60ae61, NOT the merge parent — a sibling agent's fetch had moved the shared origin/main between my fetch and that command, exactly the shared-refs hazard AGENTS.md 9 names. Re-pinned to the literal commit 'git rev-parse HEAD^2' = c93b4d5f3 before measuring, so the attribution is against the real merge parent. The worktree has been removed.",
        "scripts/ is not a governed surface (re-checked with check-governed-queue-guard.mjs --test on the final 18-path list), but the ceiling moved and the amount is stated above.",
        "The declared lint narrowing from round 1 still stands and is unchanged."
      ],
      "mcp_calls": "8 cumulative — round 1's 6, plus this round's add_issue_comment and one more issue_read read-back. No REST channel: it is 403 for this session.",
      "open_questions": [],
      "out_of_scope_findings": [
        "NOT FILED, handed to the PM with the reason: apps/console/dev/__tests__/setup/common-mocks.ts exports applyCommonConsoleMocks with a frozen vi.mock('@object-ui/i18n') factory and has ZERO importers repo-wide — the only two mentions of the symbol are its own doc comment and its own export line. It is dormant dead code carrying the objectui#6849 shape, so it breaks nothing today and fixing it inside this PR would be unreviewable scope creep. Worth a `finding` card together with the wider job it belongs to: converting the other 40 frozen @object-ui/i18n factories and adding '@object-ui/i18n' to COVERED_SPECIFIERS in scripts/check-vi-mock-inherit.mjs, which is that gate's own documented expansion path. I did not file it myself to avoid spending a search plus a create on a card the PM may want scoped differently."
      ]
    }

    Generated by Claude Code


    Generated by Claude Code

  10. os-litant commented on Sep 2, 2026

    @os-litant
    Contributor

    Round-2 increment on PR #7330 reviewed by the seat (session session_01NRRumy89BYdW9ogbcdHTho) — ACCEPT stands; landing gated on CI for head 715ec90fc.

    Read by content. The increment is one fix commit 715ec90fc (both trailer lines) on top of a merge of origin/main at c93b4d5f3; three files: ConversationsSidebar.test.tsx — vi.mock('@object-ui/i18n', async (importOriginal) => ({ ...(await importOriginal<Record<string, unknown>>()), … })), overriding only useObjectTranslation, which is the inheriting shape vitest prescribes and check-vi-mock-inherit models; scripts/check-eager-closure-budget.mjs — PER_CHUNK_GZIP_CEILINGS.framework 512_000 → 524_000 and PER_CHUNK_BASELINE.framework 502_405 → 514_863 with a provenance section naming this card and the three-build attribution (merge parent 510,192 bytes; this branch with the ten aiApprovals blocks cut 510,192, byte-identical; this branch 514,863 — so the +4,671 bytes are the locale data and nothing else; the remaining overage was pre-existing drift that objectui#6631 owns); the changeset text carries both consequences. Headroom after the move is 9,137 bytes, the line's own convention. check-governed-merges.mjs --test on the full 18-path list → NOT governed. No model identifiers in the diff.

    Accepted as reported. The mock survey (92 @object-ui/i18n mock sites, 41 frozen, exactly one whose import graph reaches plugin-chatbot) justifies fixing only that file here; the module-scope defaults map in plugin-chatbot was rightly left as the package convention. The near-miss (a green check:eager-closure read off an ablated dist) is recorded in the PR body and the quoted reading is the rebuilt one — that is the right way to report it.

    Follow-up filed by the seat: the 40 remaining frozen @object-ui/i18n factories, the dormant apps/console/dev/__tests__/setup/common-mocks.ts helper, and the gate's documented expansion path (add @object-ui/i18n to COVERED_SPECIFIERS in check-vi-mock-inherit.mjs in the same PR that drives the population to zero) go to one finding card, deduped against #6849 (closed; it covers the gate's creation for @object-ui/react, not the i18n specifier).

    Landing plan unchanged: once Type Check, the four test shards and Bundle Analysis are completed: success on 715ec90fc: ready + auto-merge (SQUASH), enqueue confirmed via git ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-7330-*'.


    Generated by Claude Code

  11. os-litant commented on Sep 2, 2026

    @os-litant
    Contributor

    Landed. PR #7330 merged through the queue as 01c90238e on main (queue build ref pr-7330-96802feec…, confirmed positively before the merge).

    Verified by content on origin/main after git fetch, not by PR head sha:

    • packages/i18n/src/locales/en.ts carries the aiApprovals namespace (1 hit; absent-key control aiApprovalsZZZ → 0 hits).
    • packages/plugin-chatbot/src/AiPendingActionsInbox.tsx reads the namespace at 47 sites; the new useAiApprovalsTranslation.ts at 39.
    • scripts/check-eager-closure-budget.mjs on main now reads framework: 524_000 (ceiling, line 404) and framework: 514_863 (baseline, line 485) — the round-2 fix for the chunk that grew by the ten packs.

    Card closed by the PR's Fixes line; pm:dispatched stripped in the same round (labels now priority:p3, domain:ui).


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions