Uh oh!
There was an error while loading. Please reload this page.
Map-valued widget titles are editable again, writing back only the active locale (#5428) - #5588
Merged
os-sales merged 1 commit intoAug 21, 2026
Conversation
…-valued widget titles (#5428) Both dashboard authoring surfaces guarded their widget-title input by making a map-valued title read-only, justified by "nothing can reach this path from stored metadata yet - I18nLabel was plain string through rc.5". The pinned @objectstack/spec is 17.0.0, whose I18nLabelSchema admits the map, and the same file family documented that widening sixty lines away. The branch was denying authors an edit rather than protecting an unreachable path, and its deferral pointed at objectui#4163, closed as completed on 2026-08-15. Adopts @object-ui/i18n's setLocalized (objectui#5301's ruling, shipped by PR #5427) at both surfaces: a save replaces only the active locale's entry and carries every other locale across untouched. Plain-string titles are unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012u2pRjcqAYtoEjgr3wwhnK
This was referenced Aug 21, 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
|
os-sales
marked this pull request as ready for review
August 21, 2026 15:45
Uh oh!
There was an error while loading. Please reload this page.
os-sales
deleted the
claude/issue-5428-dashboard-title-locale-writeback
branch
August 21, 2026 15:45
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 freeto 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#5428
Both dashboard authoring surfaces made a map-valued widget title read-only, on a premise
@objectstack/spechad already invalidated, deferring the real answer to a card that has since closed. This adopts the write-back rule that #5301's maintainer ruling settled, at both surfaces.The premises, re-verified on
origin/mainDashboardEditor.tsxguards the title input withisAuthorableTitle(defined:152, used:300/:304/:307)I18nLabelwas plainstringthrough rc.5"resolveWidgetTitlesixty lines above documents the rc.6 widening that makes a map reachable:292points at #4163, closed as completed 2026-08-15DashboardWidgetInspector.tsx:233-250carries the identical placeholder and premiseOne premise is stronger than the card states: the installed
@objectstack/specis 17.0.0 (final), not rc.6 —node -p "require('@objectstack/spec/package.json').version".I18nLabelSchemaisz.union([z.string(), z.record(z.string(), z.string())]). So the "safe without a ruling" clause has not merely expired; what the read-only branch did in practice was deny an author the ability to edit a widget title in their own locale.The change, at each surface
packages/plugin-designer/src/DashboardEditor.tsx—isAuthorableTitle(module-local, never exported) is replaced bywriteWidgetTitle, which delegates tosetLocalized. The input'svalueis the resolved active-locale string (resolveWidgetTitle, unchanged) and itsreadOnlyprop is gone; the deadread-only:*classes go with it.onChangewriteswriteWidgetTitle(widget.title, language, e.target.value).packages/app-shell/src/views/metadata-admin/inspectors/DashboardWidgetInspector.tsx— the value ternary collapses toresolveInlineI18nLabel(widget.title, locale) ?? ''(the spec resolver passes a plain string through and answersundefinedfornull, so the branch was redundant),readOnlyis gone, andonChangewritessetLocalized(widget.title, locale, e.target.value).This surface reads through the spec's
resolveI18nLabel(which #4163's ruling requires of this package) and writes through objectui'ssetLocalized. That pairing is sound becausei18nLabel-resolver-parity.test.tsholds the two resolvers limb for limb — they differ only in how each spells a miss, andsetLocalizedalways produces a hit for the locale it wrote. The comment says so, and the new round-trip assertions check it here rather than only citing it.Both doc comments are rewritten rather than re-pointed: the stale premise is replaced by the rule actually in force, and the dangling deferral is not re-filed against another tracker — a comment pointing at a closed card is how this premise survived in the first place.
No new exports.
isAuthorableTitlewas module-local and its replacement is too;setLocalizedis imported, not modified. Neither package gains a dependency — both already declare@object-ui/i18n, and@object-ui/i18nis already an eager edge of the inspector's module graph viametadata-admin/i18n.ts, so the console's eager closure is unchanged.The pin that matters
Every new assertion is a preservation assertion. "The input is no longer read-only" is equally green against a fix that flattens the map — which is the exact data loss the read-only branch existed to prevent — so each case asserts the shape of what was written: the edited locale carries the new string, every other entry is byte-identical, and the key set is unchanged.
Both directions of non-vacuity are pinned: editing under
zh-CNwriteszh-CNand leavesenalone (a fix hard-wired toenfails), a locale the map does not carry adds an entry instead of overwriting the display fallback, and a plain-string title still saves as a plain string (a fix that wrapped every edit into a map fails).Reverse verification
Both write-backs mutated to the flattening form
onChange({ title: e.target.value }), each mutation proved on disk with anchoredgrep -cin both directions plusgit diff --stat, restored by atrap ... EXIT INT TERM:Ablated run — red at both surfaces, on the preservation assertions only:
The "shows a map-valued title resolved, and EDITABLE" cases stayed green under the flattening mutation at both surfaces — the card's warning, reproduced.
Restore verified: post-restore counts back to fix-form 1 / naive 0 at both files,
git status --porcelainempty.No dist rebuild is stated because none is in this path: both test files import the component under test by relative source path, and
@object-ui/i18nis aliased topackages/i18n/srcinvitest.config.mts. Nothing here resolves through a package'sexportstodist/, so a mutation lands in the code under test directly.Gates — all on
69335280e, each quoting its own verdict lineExit codes captured before any pipe (written into the log by the command itself), never read from a
tail.pnpm --filter '@object-ui/app-shell^...' --filter '@object-ui/plugin-designer^...' buildVERDICT command-exit 0pnpm --filter @object-ui/plugin-designer type-checkPD_TYPECHECK_EXIT=0(tsc --noEmit && tsc -p tsconfig.test.json)pnpm --filter @object-ui/app-shell type-checkAS_TYPECHECK_EXIT=0(run after the dependency-closure build)pnpm --filter @object-ui/plugin-designer lintPD_LINT_EXIT=0—67 problems (0 errors, 67 warnings), all pre-existing classes, none at a changed linepnpm --filter @object-ui/app-shell lintAS_LINT_EXIT=0—2509 problems (0 errors, 2509 warnings), likewisepnpm exec vitest run packages/plugin-designer/ packages/app-shell/.../inspectors/VITEST_EXIT=0—Test Files 63 passed (63)·Tests 665 passed, 1 skipped (666)node scripts/check-control-bytes.mjsOK (scanned 4637 tracked text file(s); skipped 85 binary)node scripts/check-changeset-presence.mjs4 source file(s) of 2 released package(s) changed, and this change declares 1 changeset(s)node scripts/check-changeset-no-major.mjsNo changeset declares a major bump.node scripts/check-changeset-fixed.mjsAll workspace packages are in the changeset fixed group.node scripts/check-lint-coverage.mjs46/46 packages linted, 0 with outstanding errorsnode scripts/check-type-check-coverage.mjs45/46 via type-check ... 41/41 packages compile their testsnode scripts/check-skills-paths.mjs95/96 stated path(s) resolveThe type-checks above are a re-run on the final commit. The first pass ran before a test-helper fix, so its verdict described a tree that was no longer HEAD; both were re-run at
69335280erather than reported from the earlier run.node scripts/check-control-bytes.mjsis complemented by a self-scan of the five changed files for the bytes a gate that scans tracked files would miss on an unstaged tree:grep -naP '[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]', no matches.Declared narrowing
Repo-wide
pnpm lintwas not run locally; CI owns it. The evidence that this narrowing cannot hide a failure:lintisturbo run lint, and each package's script is plaineslint .. The repo-wide run is exactly the union of the 46 per-package runs.eslint .reaches.projectServiceand noparserOptionsanywhere ineslint.config.js, and neither package carries its own config), so a change inside these two packages cannot move a lint verdict on a file in any of the other 44.check-eager-closure-budget.mjswas not run: it readsapps/console/dist/eager-closure.json, emitted only by avite buildof the console, and it says so itself ("This is a broken gauge, not a passing budget"). CI-owned. The reasoning above for why this diff adds no closure edge is an argument, not a measurement.Scope
A full multi-locale editor stays out of scope and is not built here — an author still reaches only the entry for the locale they are in. That remains an open product question; see the report comment on #5428 for why it is deliberately not re-filed against a tracker.
Generated by Claude Code