fix(ui): v1-honest start chips, assistant panel scroll, toast lifetime, and the remaining built-in aggregate captions - #7710
Merged
Conversation
…e, and the remaining built-in aggregate captions - cloud#1984: reword the five maker start chips in all ten packs (and the call-site defaultValue fallbacks) so they ask only for what ADR-0112 v1 builds; note beside the keys to revert when v2 re-adds flows. - objectui#7481: add step labels for get_authoring_rules, load_tools, open_record, test_flow and toggle_flow, which are newer than the pinned spec's platform-tool registry. - objectui#7480: re-arm StickToBottom on every send path so the assistant rail follows the thread; message appends deliberately do not. - objectui#7482: drop the console's bottom-right toaster override — the corner belongs to the ChatDock composer, where a resting pointer also pauses sonner's dismiss timer indefinitely. - objectui#7534: resolve builtinAggregate through buildDatasetFieldHelpers at the five headerLabel call sites, so a KPI/table/pivot/preview caption reads what the chart legend beside it reads. - objectui#7476: gate the sys_activity read on the object registry the shell already loads; every uncertainty still reads.
This was referenced Sep 5, 2026
Contributor
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Six small user-visible fixes on the maker / assistant / dataset surfaces, batched into one PR because they are all in this repo and none is bigger than a file or two. Verified on
6129b3b0c.References
cloud#1984. Closes #7476, Closes #7480, Closes #7481, Closes #7482, Closes #7534..objectui-shabump before any of this reaches the cloud product — nothing here ships to a tenant environment until that pin moves.cloud#1984 — the maker's start chips promised what v1 cannot deliver
The five chips on the environment home's "用 AI 搭建" are the product's own recommendations, and two of them asked for automation ADR-0112 v1 (cloud#1956 / PR #1970) does not author: the ticket chip said 「状态流转」, the inventory chip said 「低库存预警」. The measured behaviour was not a refusal — the model degraded them into a status kanban and a low-stock view — so the chip promised an alert and delivered a page.
All five reworded in all ten packs, plus the
defaultValuefallbacks inmetadataAssistantSuggestions(), which are a second copy of the same strings and would otherwise have kept the old promises on the surface with the least i18n. Each stays a real business scenario and asks only for objects, fields, views, pages, dashboards and sample data — the ticket chip now asks for a status field and a board grouped by it, the inventory chip for a view that filters items below their reorder point. A REVERT note sits beside the keys in every pack and at the call site, naming ADR-0112 v2.Also checked but NOT changed, and filed instead as objectui#7709: the maker's EDIT-mode starter
console.ai.suggestions.editApp.addAutomation(「加一个自动化 —— 审批、状态流转或通知」) is the same defect class one chip family over, but it has no v1-honest rewording that does not duplicate the three chips beside it — dropping it for v1 vs. replacing it with a different v1 capability is a product call, not a translation fix.Verified:
packages/i18n/src/__tests__/makerStartChips-v1-scope-1984.test.ts(32 cases) — all five keys present and non-empty in each of the ten packs, no chip matching that pack's OWN automation vocabulary (English-only scanning would have declared the five non-Latin packs clean without reading a character of them), and a non-vacuity control that re-runs each banned list against the wording that pack actually shipped before this PR and requires it to be flagged. PlusAiChatPage.startChips-1984.test.ts(4 cases) for the call-site copy, asserting byte-equality with theenpack.objectui#7481 —
Get authoring ruleswas unlocalized among Chinese step labelsget_authoring_rules(cloud#1837) had nochatbot.tool.*entry, so it fell through tohumanizeToolName's English title-caser. Checking the other recently-added tool names found four more in the same state:load_tools,open_record,test_flow,toggle_flow. All five are registered by cloudservice-ai-studio'splugin.tsbut are newer than the pinned@objectstack/spec'sPLATFORM_TOOLS_BY_PACKAGEsnapshot. Labels added to all ten packs.The root cause of the drift is upstream and filed as cloud#1995: the conformance test that exists to catch exactly this hand-lists four fewer definition groups than
plugin.tsregisters, so it asserts a stale list against an equally stale registry and stays green.Verified:
packages/plugin-chatbot/src/__tests__/toolLabels-locale-parity-7481.test.ts(17 cases). Coverage isregistry ∪ AHEAD_OF_PIN, so it becomes a real ratchet the moment the pin advances; a separate case fails if a name is left in the hand-held list after the registry lists it, which is what keeps that list shrinking to zero rather than becoming a second registry. Also pins that theenlabels are byte-equal to the title-caser (the packs' own stated rule, previously unchecked) and that an unknown/third-party tool still degrades to English.objectui#7480 — the assistant rail did not follow the thread after a send
Worth stating because the card's premise reads as a code divergence and is not one: the rail and the full-page maker are the same component (
ChatDock→ChatPane→ChatbotEnhanced→Conversation). What differs is width.StickToBottomauto-follows only while the view is at the bottom; in a ~360px rail a reply is two or three times taller than in the full-page column, so by the time the user has read it the lock is escaped and the next send appends off-screen.Every send path now re-arms the lock via the library's own
contextRefescape hatch — so the vendored ai-elementselements/conversation.tsxis not edited. That includes the plan card's "Build it" and the 确认修改 approval, whose own code comment already said "the approval's visible effect lands at the BOTTOM of the thread — outside the viewport when the plan card is scrolled into view". Message APPENDS deliberately do not scroll, so a user reading back through the thread while the answer streams is never yanked to the bottom.Verified:
ChatbotEnhanced.followOnSend-7480.test.tsx(4 cases) — acontextRefis handed to the conversation at all; a typed send and a start-chip send each scroll; and two successive streaming appends do not.objectui#7482 — the success toast covered the send button and never dismissed
One defect, two symptoms.
apps/consolepinned the toaster tobottom-right, an override that predates ADR-0057 P3a and the corner it gave to the ChatDock composer and its FAB. So a toast covered the send button — and sonner pauses a toast's dismiss timer whenever the pointer is inside the toaster region:expandedis set by that region's ownonMouseEnter/onMouseMove, so a pointer resting on the composer underneath held the timer at zero. The 4s default was never wrong; it never got to run. The override is gone and the console takesConsoleToaster's own documented top-right anchor.On the second half of the ask — "errors may persist" — deliberately not done, and the reason is written on the
duration: sonner'sToasterhas no per-type duration, so saying it means adurationat each of the ~100toast.error(...)call sites.closeButtonalready gives every toast a manual exit. Success toasts are at 4s, inside the 3–5s band the card asked for, and that is now pinned — nothing checked it before.Verified:
ConsoleToaster.autoDismiss-7482.test.tsx(3 cases: the default anchor is top-right, the override still works, and 「客户更新成功」 is present at 2.5s and gone by 5.5s) andconsoleToasterAnchor.ratchet-7482.test.ts(3 cases) which readsapps/console/src/App.tsxfrom source and refuses a bottom-right re-anchor, with its own non-vacuity control.objectui#7534 — built-in aggregate captions still printed the server's
CountbuildDatasetFieldHelpers().headerLabelnow takes an optionalbuiltinAggregateLabelsand resolves throughresolveMeasureLabelfirst — the seam objectui#7535/#7258 already landed forbuildChartSeries()— so there is ONE resolution order rather than two, and the five call sites pass it: the dashboardDatasetWidget(KPI caption + table/pivot headers), the three sites inDatasetReportRenderer(summary header, metric caption / chartmeasureLabel, matrix header), and app-shell'sDatasetPreview. Omitting the argument reproduces the previous output byte for byte.Verified:
dataset-format.builtinAggregate-7534.test.ts(10 cases — the full resolution order, every member of the closed vocabulary, an author-declared measure kept verbatim, a field literally namedcountwith no discriminator kept verbatim, an unrecognised discriminator, an unresolved and an empty label, and that omitting the argument changes nothing) plusDatasetWidget.builtinAggregateHeader-7534.test.tsx(5 cases) which renders the widget under a realI18nProviderand reads the KPI caption and the table header — the wiring half, which no pure test can see.objectui#7476 —
sys_activity404 on every openPremise partly retracted, and the retraction is why the change is small. "Handle the absence quietly" is already implemented, at four layers: the adapter memoizes the missing collection so no second request goes out, its
createQuietHttpLoggerdemotes the 404 toconsole.debug,sharedUserFeedsretires the feed as an ANSWER (ready, noterror, with a bounded 3-probe budget), and the panel renders its earned 「暂无最近动态」. None of that changes here. What was left is the one doomed request per page load, anddata-objectstackstates the rule for exactly that case in its own comment: "The cure for doomed requests is not issuing them, never hiding them once issued."So new
useObjectPresencereads the object registry the shell already loads for the nav (sys_*objects are in it where they exist —AppHeaderfilters them out of the app-object picker by name, and the console resolves/apps/{any app}/sys_activityas an ordinary object route). Absence has to be earned, because the two mistakes are not symmetric: a missed skip costs one request that already degrades correctly, while a wrong skip costs a real deployment its feed with no error anywhere. Only a registry that has ANSWERED and lists other objects without this one skips the read.The clause that would otherwise have shipped this broken:
useMetadata()outside a<MetadataProvider>returns a frozen no-op whosegetTypeStatussays'ready'and whosegetItemsByTypesays[]— a shape that reads exactly like "answered, and your object is not there". An empty registry therefore reads asunknown.Verified:
sharedUserFeeds.activityGate-7476.test.tsx(14 cases). Four of the six behavioural cases are "still reads" on purpose — no provider, empty registry, errored registry, present — one is "asks nothing yet" (still loading), and only the tenant-registry case is "no request at all". The pure predicate is pinned across all six status × registry-contents combinations.Verification
Host is macOS on Node v26.7.0; CI runs Node 22.x. The shared heavy-verify lock is Linux-only (
flockis util-linux), so it reports DECLARED UNLOCKED MODE here and provides no exclusion — noted rather than claimed.pnpm --filter '@object-ui/app-shell...' --filter '@object-ui/plugin-report...' --filter '@object-ui/plugin-dashboard...' --filter '@object-ui/plugin-chatbot...' --filter '@object-ui/console...' run buildpnpm --filter core --filter i18n --filter plugin-chatbot --filter plugin-dashboard --filter plugin-report --filter app-shell --filter console run type-checkDonepnpm exec vitest run packages/i18n/ packages/core/ packages/plugin-chatbot/ packages/plugin-dashboard/ packages/plugin-report/Test Files 322 passed (322),Tests 4937 passed (4937)pnpm exec vitest run packages/app-shell/ apps/console/Test Files 1 failed | 708 passed (709),Tests 7 failed | 6920 passed | 1 skipped— see below6129b3b0cpnpm exec vitest run <the nine>Test Files 9 passed (9),Tests 91 passed (91)pnpm lintTasks: 47 successful, 47 total,0 errors(2883 pre-existing warnings)eslint --format json <the 30 changed .ts/.tsx>no-explicit-any/ fast-refresh in files this PR touches by a few linescheck:i18n-keys,check:i18n-drift,check:i18n-dead-keyscheck-changeset-presence.mjs,check-changeset-no-major.mjspnpm check:control-bytesThe one red suite is pre-existing, proven
packages/app-shell/src/console/__tests__/anonSeedScope-5746.enumeration.test.tsxfails 7/7 withexpected 0 to be greater than 0on its own counter-probes — the instrument records zero sessionStorage writes, so nothing downstream of it can be measured.Checked against the merge-base rather than asserted: with the branch committed,
git checkout 8ad218d5 -- packages appsreverted the working tree to the base content (git diff --stat HEADshowed the 20 files reverting, 470 deletions), the file was re-run there and failed identically, 7/7. The tree was then restored withgit checkout HEAD -- packages appsand the restore proven bygit diff HEADbeing empty — not by the command's exit code.It also fails standalone, so it is not load-flakiness. The likeliest cause is the host's Node 26 (
localStorage is not available because --localstorage-file was not providedis emitted throughout) shadowing jsdom's storage; CI runs Node 22.x and will say. Reported rather than filed, since it reproduces only on this host so far.Ablation
The two i18n suites carry their non-vacuity controls in the file rather than as a one-off run: each locale's banned-vocabulary list is re-run against the exact wording that pack shipped before this PR and must flag it, and the toaster ratchet's matcher is re-run against the
position="bottom-right"line it replaced. A list that silently stops matching therefore fails the suite instead of passing it — which is the failure mode a manual ablation only rules out once.🤖 Generated with Claude Code