fix(studio,chatbot,i18n): workbench labels over internal ids, localized property panel, one tool-card status - #7366
Merged
Conversation
…ed property panel and tool cards The Studio workbench showed a Chinese author internal identifiers and whole blocks of English, and the AI tool card's header disagreed with its own body. - Interfaces breadcrumb / canvas caption / nav rail: metadata label plus a translated kind; the internal `type · name` pair moves to the tooltip. An unlabelled nav leaf falls back to its object name instead of an empty row. - `parsePackages` reads the package name from BOTH positions the packages endpoint serves it in (the server's own list handler already reads `item.manifest?.id || item.id` on both halves) — a registry-shaped entry no longer degrades to showing its reverse-domain id as its name. - Dashboard property panel: the spec's English authoring form is overlaid through the platform's own `metadataForms.<type>` convention, using `resolveMetadataFormLabels` from `@objectstack/spec/system` rather than a second key scheme — which also gives the `header` composite's sub-rows. - Tool cards: titles resolve through `chatbot.tool.<name>` (the closed `PLATFORM_TOOLS_BY_PACKAGE` registry, ten packs), the header status badge is localized through an additive `statusLabels` seam on the vendored element, and the plan count strip is a real plural family. - `resolveProposalCardState` is now the one producer of "how far has this proposal got"; the header badge reads it instead of deriving from the tool's own output alone, so a confirmed / built / published card stops saying "Awaiting Approval". Fixes #7254 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…re pair Moving `type · name` off the breadcrumb and the canvas caption put the SAME tooltip string on three elements (the rail item already had it), which made every `findByTitle` pin in the Interfaces suite ambiguous — and left a tooltip that shows a cryptic pair without saying what it is. Prefix both with a localized "Internal id" label, and update the one pin that asserted the caption's old raw text. Part of #7254 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`any[]` in the new pins was the one lint finding this PR actually introduced (everything else the targeted run reports is pre-existing on the touched files). Two small interfaces cover exactly what the assertions read. Part of #7254 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This was referenced Sep 2, 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
|
hotlong
marked this pull request as ready for review
September 2, 2026 13:28
This was referenced Sep 2, 2026
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.
Fixes #7254
A Chinese customer opening the Studio workbench met three separate failures of the
same kind: internal identifiers printed where a name belongs, whole English blocks
inside an otherwise Chinese screen, and a tool card whose header contradicted its
own body.
1. Workbench labels, internal names on the tooltip
dashboard · customer_dashboardbeside aChinese label — the same strip speaking two vocabularies. They now read the metadata
label plus the translated kind (
客户仪表盘 · 仪表板), with the internal pairon the tooltip, prefixed so it says what it is (
内部标识: dashboard · …) rather thanrepeating a bare cryptic pair. The prefix is also what keeps three elements from
sharing one addressable tooltip string.
localized rail, and a leaf the author never labelled rendered an empty row. It now
falls back to the object name — a poor label, but an honest one.
parsePackagesread the package'sidfrom BOTHpositions the packages endpoint serves it in, and its
namefrom only one. So everyregistry-shaped row (
protocol.getMetaItems({type:'package'})hands back the packageMETADATA DOCUMENT, fields top-level) lost its human name and the top bar showed
app.b2r4. Closing that asymmetry inside the one reader that already declares bothpositions — the server's own list handler reads
item.manifest?.id || item.idon bothhalves, so this is not a new tolerant dialect.
scopeis deliberately leftmanifest-only (it decides what the switcher HIDES, a different question).
2. Dashboard property panel
The panel is spec-driven on purpose:
dashboardFormfrom@objectstack/spec/uifedstraight into
SchemaForm, so a new spec prop shows up with zero code changes. The specauthors that copy in English, and nothing on this side translated it.
Rather than invent a parallel key scheme, the new
metadata-form-i18n.tssupplies atranslation bundle to the platform's own resolver —
resolveMetadataFormLabelsfrom
@objectstack/spec/system, the same function the framework's/meta/typeshandlercalls to localize these very forms. That buys the producer's section-name slugging, its
dot-path field addressing, and its composite sub-field synthesis (which is what produces
the
header.showTitle/showDescription/actionsrows the card listed). When anenvironment's own bundle reaches this surface it drops in as a higher-precedence source
with no call-site change.
3. Tool card status — one producer
cloud#787's shape, mirrored. The header badge derived "Awaiting Approval" fromisProposalResult(tool.result)alone — a fact about the tool's own output, which neverchanges once it has returned — while the body collapsed to 已搭建 / 已生效 / 未生效 from
four other memos the header never read. Fixing it in the header's own branch would have
made a FIFTH producer, so
resolveProposalCardStateis now the single one both sidesread, with a table mapping its four states onto the badge.
i18n coverage report
No coverage script exists in this repo for these families, so it was measured directly:
a throwaway vitest module imported the ten locale packs plus
PLATFORM_TOOLS_BY_PACKAGEfrom@objectstack/spec, counted defined keys per pack, andran the real overlay over
dashboardFormto dump every string the panel renders. Run at2d332f868; the module was deleted afterwards (nothing in this diff).AI tool cards,
@object-ui/i18npackschatbot.tool.*chatbot.toolState.*chatbot.plan.*Platform tool names in
PLATFORM_TOOLS_BY_PACKAGE: 30; keyed here: 30; unkeyed:none. The scope is that closed registry rather than the six names the card happened
to list — the maintenance contract for adding a tool already lives there, and partial
coverage is exactly the dormancy this fixes.
envalues equal what the existing Englishtitle-caser produced, so adding the keys cannot silently reword the English UI (the one
deliberate exception: the badge's "Awaiting Approval" becomes "Awaiting approval", which
is what the activity chips beside it already said).
Dashboard property panel, every string it renders after the overlay
35 strings rendered; 1 still carries a 4+ letter Latin word —
snake_case 唯一标识,which is the identifier convention's own name and is deliberately not translated. All
seventeen strings the card enumerated are covered.
Declared deviation from the dispatch ruling
The ruling said the property panel's strings go through the
@object-ui/i18ndictionary. They go through the
engine.*carve-out beside them instead(
views/metadata-admin/, en+zh), and this is flagged rather than done quietly:(
engine.inspector.dashboard.*,engine.studio.*) — splitting one panel across twodictionaries is worse to maintain than either alone;
metadata-admin/i18n.tsheader,packages/i18n/README.md"Scope — theengine.*carve-out") with a deliberatelynarrow reopening condition;
@object-ui/i18nenforces full key parity across ten packs, so the alternative wasinventing technical form copy in eight more languages nobody asked for.
The user-visible outcome the ruling demanded — full Chinese coverage of the listed
strings — is met either way; the tool-card half is in
@object-ui/i18nas directed.Producer-side findings, reported not guessed
Contract-first: two English strings on that panel are the spec's own copy and are NOT
rewritten here.
dashboardForm.gap.helpTextisGrid gap (Tailwind units)— a unit only a developercan act on. The zh overlay says what an author can decide; the English source is
packages/spec/src/ui/dashboard.form.ts's to fix.objectstack's generatedzh-CN.metadata-forms.generated.tscarries a staledashboard.sections.layout.description(栅格与响应式) against a source that now says"Grid sizing and refresh cadence." — the extractor's merge only fills gaps, so a
present-but-stale leaf is never corrected. Worth a card on that side.
PackageSwitcher's package-list fetch swallows any failure (.catch(() => {})),which leaves the top bar showing the raw package id forever with no signal. Out of
scope here (it is the fetch's error posture, not the naming) — reported for triage.
Tests
Run at
2d332f868(final commit;git rev-parse --short HEAD), afterpnpm install+ a dependency-closure build on the rebased tree.pnpm exec vitest run packages/plugin-chatbot/ packages/i18n/ packages/app-shell/src/views/studio-design/— 136 files, 1592 tests, 0 failedpnpm exec vitest run packages/app-shell/src/views/metadata-admin/ packages/app-shell/src/preview/ packages/app-shell/src/console/ai/— 254 files, 2533 passed, 1 skippedpnpm --filter @object-ui/{i18n,plugin-chatbot,app-shell} type-check— all exit 0. Eachruns
tsc --noEmit && tsc -p tsconfig.test.json, so the new test files are coveredrather than excluded.
pnpm --filter @object-ui/{i18n,plugin-chatbot,app-shell} build— allDone.check-i18n-call-site-keys"Every in-scope call-site key resolves against the en pack(2952 keys) …";
check-i18n-en-drift"No en value changed in this range." (46 keysadded, 0 changed);
check-control-bytes"OK (scanned 6048 tracked text file(s))";check-changeset-presence"24 source file(s) of 3 released package(s) changed, andthis change declares 1 changeset(s)";
check-changeset-no-major"No changeset declaresa
majorbump."Reverse verification (run from the committed state, so the restore leg had a real
reference): the header-state fix was reverted on disk with
perl -0pi, the mutationproved by grep counts (injected 1 / removed 0) and a changed
git hash-object, andtoolCardHeaderState-7254.test.tsxwent 3 failed / 4 passed — exactly the three pinsthat assert the header follows the body; the unanswered-proposal case and the four unit
cases stayed green, which is correct because
resolveProposalCardStatestill existed.Restored via
git checkout HEAD -- path, proved by an emptygit diff HEAD.Lint, a declared narrowing. The repo-wide
pnpm lintis CI's run; here eslint wasrun on exactly the files this PR changes, and every message attributed to a changed line
by cross-referencing
git diff -U0. Three facts, so the narrowing is a measurement andnot a skip: (a) the population came from eslint's own config resolution over the 24
changed
.ts/.tsxpaths, not from a guess about which files count; (b) the file countis eslint's own
--format jsonoutput — 24 files, 1 error, 36 warnings; (c) this repodoes not enable type-aware linting, so nothing in this diff can move a verdict on an
untouched file. Exactly one message lands on a line this PR wrote:
react-refresh/only-export-componentson the newly exportedresolveProposalCardState— a warning, and the sixth of that same family already firing in that file (839, 942,
951, 1141, 3659). The single error,
react-hooks/static-componentsinStudioDesignSurface.tsx, is pre-existing: lintingorigin/main's own copy of that filereports the identical error (line 2744 there, 2773 here — same code, shifted by this
diff's insertions).
Not measured / known-red elsewhere.
packages/app-shell/src/console/__tests__/anonSeedScope-5746.enumeration.test.tsxfails 7/7 — and fails identically with this PR's source changes reverted to the merge
base, so it is pre-existing and unrelated. The whole-package
app-shellrun exceeds thecontainer's 10-minute foreground cap; the two directories this diff touches were run in
full instead, plus
preview/andconsole/ai/(the other consumers of the changedcomponents, found by grepping the tests for the strings this PR moves).
Overlap with in-flight work
Rebased onto
ab9294039. One conflict, inpackages-io.ts, against #7331 ("Studiopackage switcher reads the server's writable verdict"): resolved as the union of both
intents — their server-computed
writableverdict kept verbatim, this card'sname-from-either-position added beside it, and the doc comment extended to say why
writablereading only the top level is a different thing from thenameasymmetry(it is the server's own computed field, which the manifest does not carry). No side was
taken wholesale.
One pin was updated rather than added:
StudioDesignSurface.interfacesAction.test.tsxasserted the canvas caption's old raw
action · sync_nowtext. That text is preciselywhat this card removes, so the assertion moves to the caption's testid — its label plus
the internal pair on the tooltip.
Generated by Claude Code