Skip to content

fix(fields,core,detail): make the sharing-rule dialog usable — i18n, a picker that lists people, and permission-aware CTAs - #2920

Merged
os-zhuang merged 3 commits into
mainfrom
claude/sharing-widgets-i18n-3821
Jul 28, 2026
Merged

fix(fields,core,detail): make the sharing-rule dialog usable — i18n, a picker that lists people, and permission-aware CTAs#2920
os-zhuang merged 3 commits into
mainfrom
claude/sharing-widgets-i18n-3821

Conversation

@baozhoutao

Copy link
Copy Markdown
Contributor

Fixes the Console's "New sharing rule" dialog, which could not produce a working rule. Server-side half is objectstack-ai/objectstack#3821 / objectstack-ai/objectstack PR (framework branch claude/sharing-rules-i18n-recipient-02a623).

What was broken

Symptom Cause
Recipient picker showed "No matches" for every type $orderby: 'name asc' fell into the Object.entries branch of queryParamsToRecord and was walked character by character into 0 n,1 a,2 m,…; the server sorted by columns that don't exist and returned nothing
Dialog copy was English in a Chinese Console The three sharing-rule widgets hardcoded their strings and never used useFieldTranslation
Reopening a saved rule blanked the recipient The picker's type-change reset treated the edit form's '' → 'user' hydration as a type switch, and the blank was then persisted on save
"Add filter" fired validation / could save early No FilterBuilder control set type="button"; a bare <button> in a <form> defaults to submit
A rejected save showed FORBIDDEN: insufficient privileges to update showcase_private_note pi-TgoJ4_DM55Fqz The form rendered error.message verbatim — untranslated, and leaking the object machine name and record id
"Edit" offered on read-only records Object permissions can't express "this one record is read-only", which is exactly what a read-only sharing grant is

ObjectGrid builds the same string $orderby from a schema-level sort in three places, so this also fixes grids that silently rendered an empty table.

Notable design points

  • The recipient placeholder is keyed per type, not interpolated. `Select a ${type.replace(/_/g,' ')}` is a shape no locale can translate.
  • The record-level gate consults the explain engine (POST /api/v1/security/explain with a recordId, ADR-0090 D6 / ADR-0095 C2) — the same pipeline the enforcement middleware runs, so button and server can't disagree. Explaining oneself needs no special permission.
  • It fails open everywhere: no answer yet, non-OK response, a deployment without plugin-security (501), a split SPA origin. A courtesy hint must never be why a permitted user can't act; the server stays the authority (ADR-0057 D10).
  • ⚠️ Reviewer call: the gate costs one cached explain request per record open (skipped when the object-level check already says no). Happy to move it behind a server-side per-record access hint on the record read instead if you'd rather not pay that.

Verification

Browser-driven against the framework showcase Console in Chinese — data, accounts, rules and checks all created through the UI, no API calls:

  • Copy localized throughout; picker lists real users / business units / positions (empty "teams" is genuinely 0 rows)
  • A rule saved end-to-end stores a real record id; reopening it keeps recipient + criteria
  • Editing the filter no longer submits the dialog
  • Read-only recipient: no Edit button; same recipient at edit level: Edit present, save succeeds; the user's own records keep Edit throughout
  • Denied save now reads 「您没有权限保存这条记录。」 — no machine name, no record id

Tests: plugin-detail + permissions 303 ✓, components 319 ✓, fields/core/i18n suites ✓. Each new test verified to fail without its fix. Two known-unrelated flakes on this machine: a TZ-dependent DATEFORMAT case that also fails on main, and ReportPreview.dataset timing out only under full-suite load (passes in isolation on both branches).

🤖 Generated with Claude Code

@vercel

vercel Bot commented Jul 28, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectui Ignored Ignored Jul 28, 2026 12:04pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Main entry (gzip) 27.9 KB 350 KB
Entry file index-Bc_iRxSz.js
Status PASS

📦 Bundle Size Report

Package Size Gzipped
app-shell (index.js) 8.20KB 2.97KB
app-shell (runtime-config.js) 7.42KB 2.32KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 7.57KB 2.97KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 1.17KB 0.53KB
auth (AuthProvider.js) 22.10KB 4.37KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.12KB 3.41KB
auth (LoginForm.js) 17.86KB 5.29KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.43KB 2.09KB
auth (SocialSignInButtons.js) 9.60KB 3.89KB
auth (UserMenu.js) 3.40KB 1.22KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 35.76KB 9.11KB
auth (createAuthenticatedFetch.js) 4.37KB 1.69KB
auth (index.js) 2.25KB 1.01KB
auth (org-roles.js) 6.72KB 2.85KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 4.91KB 0.87KB
auth (useIsWorkspaceAdmin.js) 1.61KB 0.85KB
collaboration (CommentThread.js) 18.38KB 4.49KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 3.65KB 1.42KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.25KB 0.53KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 450.01KB 97.89KB
core (index.js) 2.12KB 0.77KB
create-plugin (index.js) 9.28KB 2.98KB
data-objectstack (index.js) 127.78KB 32.15KB
fields (index.js) 220.04KB 53.90KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (i18n.js) 4.32KB 1.77KB
i18n (index.js) 2.46KB 0.96KB
i18n (pickLocalized.js) 1.70KB 0.83KB
i18n (provider.js) 5.37KB 1.72KB
i18n (useObjectLabel.js) 25.17KB 5.80KB
i18n (useSafeTranslation.js) 3.26KB 1.44KB
layout (index.js) 38.45KB 10.67KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.74KB
mobile (index.js) 1.50KB 0.62KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 4.42KB 1.27KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.71KB 0.42KB
mobile (useResponsiveConfig.js) 1.36KB 0.63KB
mobile (useSpecGesture.js) 1.77KB 0.77KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 6.84KB 2.42KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 3.67KB 1.12KB
permissions (evaluator.js) 4.41KB 1.44KB
permissions (index.js) 0.91KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.52KB
permissions (usePermissions.js) 1.55KB 0.71KB
plugin-ai (index.js) 15.71KB 3.79KB
plugin-calendar (index.js) 44.90KB 12.35KB
plugin-charts (index.js) 57.26KB 16.24KB
plugin-chatbot (index.js) 179.93KB 42.67KB
plugin-dashboard (index.js) 109.60KB 28.33KB
plugin-designer (index.js) 210.56KB 42.56KB
plugin-detail (index.js) 216.30KB 52.88KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 103.47KB 25.10KB
plugin-gantt (index.js) 162.26KB 39.53KB
plugin-grid (index.js) 179.25KB 46.97KB
plugin-kanban (index.js) 47.82KB 13.18KB
plugin-list (index.js) 98.67KB 23.35KB
plugin-map (index.js) 16.80KB 5.24KB
plugin-markdown (index.js) 13.65KB 4.67KB
plugin-report (index.js) 37.07KB 9.81KB
plugin-timeline (index.js) 25.03KB 7.11KB
plugin-tree (index.js) 8.36KB 2.81KB
plugin-view (index.js) 85.68KB 20.85KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.55KB 0.67KB
providers (UploadProvider.js) 11.71KB 3.53KB
providers (index.js) 0.44KB 0.22KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 3.19KB 1.38KB
react (LazyPluginLoader.js) 3.77KB 1.33KB
react (SchemaRenderer.js) 18.70KB 6.09KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.00KB 0.55KB
sdui-parser (codegen.js) 4.09KB 1.74KB
sdui-parser (index.js) 2.16KB 0.94KB
sdui-parser (parse.js) 10.04KB 2.82KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 4.69KB 1.48KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 0.99KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 0.20KB 0.18KB
types (crud.js) 0.20KB 0.18KB
types (data-display.js) 0.20KB 0.18KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 0.77KB 0.41KB
types (disclosure.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (index.js) 1.86KB 0.91KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 0.20KB 0.18KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (spec-report.js) 5.04KB 1.93KB
types (system-fields.js) 2.39KB 1.17KB
types (theme.js) 0.20KB 0.18KB
types (ui-action.js) 0.75KB 0.46KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Main entry (gzip) 27.9 KB 350 KB
Entry file index-CRIh5yo5.js
Status PASS

📦 Bundle Size Report

Package Size Gzipped
app-shell (index.js) 8.20KB 2.97KB
app-shell (runtime-config.js) 7.42KB 2.32KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 7.57KB 2.97KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 1.17KB 0.53KB
auth (AuthProvider.js) 22.10KB 4.37KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.12KB 3.41KB
auth (LoginForm.js) 17.86KB 5.29KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.43KB 2.09KB
auth (SocialSignInButtons.js) 9.60KB 3.89KB
auth (UserMenu.js) 3.40KB 1.22KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 35.76KB 9.11KB
auth (createAuthenticatedFetch.js) 4.37KB 1.69KB
auth (index.js) 2.25KB 1.01KB
auth (org-roles.js) 6.72KB 2.85KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 4.91KB 0.87KB
auth (useIsWorkspaceAdmin.js) 1.61KB 0.85KB
collaboration (CommentThread.js) 18.38KB 4.49KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 3.65KB 1.42KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.25KB 0.53KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 450.13KB 97.92KB
core (index.js) 2.12KB 0.77KB
create-plugin (index.js) 9.28KB 2.98KB
data-objectstack (index.js) 127.78KB 32.15KB
fields (index.js) 220.07KB 53.90KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (i18n.js) 4.32KB 1.77KB
i18n (index.js) 2.46KB 0.96KB
i18n (pickLocalized.js) 1.70KB 0.83KB
i18n (provider.js) 5.37KB 1.72KB
i18n (useObjectLabel.js) 25.17KB 5.80KB
i18n (useSafeTranslation.js) 3.26KB 1.44KB
layout (index.js) 38.45KB 10.67KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.74KB
mobile (index.js) 1.50KB 0.62KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 4.42KB 1.27KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.71KB 0.42KB
mobile (useResponsiveConfig.js) 1.36KB 0.63KB
mobile (useSpecGesture.js) 1.77KB 0.77KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 6.84KB 2.42KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 3.67KB 1.12KB
permissions (evaluator.js) 4.41KB 1.44KB
permissions (index.js) 0.91KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.52KB
permissions (usePermissions.js) 1.55KB 0.71KB
plugin-ai (index.js) 15.71KB 3.79KB
plugin-calendar (index.js) 44.90KB 12.35KB
plugin-charts (index.js) 57.26KB 16.24KB
plugin-chatbot (index.js) 179.93KB 42.67KB
plugin-dashboard (index.js) 109.60KB 28.33KB
plugin-designer (index.js) 210.56KB 42.56KB
plugin-detail (index.js) 216.30KB 52.88KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 103.47KB 25.10KB
plugin-gantt (index.js) 162.26KB 39.53KB
plugin-grid (index.js) 179.25KB 46.97KB
plugin-kanban (index.js) 47.82KB 13.18KB
plugin-list (index.js) 98.67KB 23.35KB
plugin-map (index.js) 16.80KB 5.24KB
plugin-markdown (index.js) 13.65KB 4.67KB
plugin-report (index.js) 37.07KB 9.81KB
plugin-timeline (index.js) 25.03KB 7.11KB
plugin-tree (index.js) 8.36KB 2.81KB
plugin-view (index.js) 85.68KB 20.85KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.55KB 0.67KB
providers (UploadProvider.js) 11.71KB 3.53KB
providers (index.js) 0.44KB 0.22KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 3.19KB 1.38KB
react (LazyPluginLoader.js) 3.77KB 1.33KB
react (SchemaRenderer.js) 18.70KB 6.09KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.00KB 0.55KB
sdui-parser (codegen.js) 4.09KB 1.74KB
sdui-parser (index.js) 2.16KB 0.94KB
sdui-parser (parse.js) 10.04KB 2.82KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 4.69KB 1.48KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 0.99KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 0.20KB 0.18KB
types (crud.js) 0.20KB 0.18KB
types (data-display.js) 0.20KB 0.18KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 0.77KB 0.41KB
types (disclosure.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (index.js) 1.86KB 0.91KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 0.20KB 0.18KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (spec-report.js) 5.04KB 1.93KB
types (system-fields.js) 2.39KB 1.17KB
types (theme.js) 0.20KB 0.18KB
types (ui-action.js) 0.75KB 0.46KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

baozhoutao and others added 2 commits July 28, 2026 07:01
…a picker that lists people, and permission-aware CTAs (objectstack#3821)

The Console's "New sharing rule" dialog could not produce a working rule.

**The recipient picker listed nothing, ever.** `QueryParams['$orderby']` was
typed as `Record | string[] | SortObject[]`, so `queryParamsToRecord` fed any
non-array value to `Object.entries`. Handed the clause string `'name asc'` —
which callers do build by hand — it walked the string index by index and emitted
`$orderby=0 n,1 a,2 m,…`. The server sorted by columns that don't exist and every
row was filtered out, so `recipient_id` showed "No matches" for every recipient
type. `ObjectGrid` builds the same shape from a schema-level `sort` in three
places, so grids with a string sort silently showed an empty table. A string
`$orderby` is now passed through verbatim; the type admits `string`, and
`RecipientPickerField` switched to the structured form so it can't regress.

**The three sharing-rule widgets never had translations.** `ObjectRefField`,
`RecipientPickerField` and `FilterConditionField` hardcoded their copy, so a
Chinese Console showed "Select an object", "Select a user", "No matches". They
now go through `useFieldTranslation`, with keys in all ten locales. The
recipient placeholder was the interesting one: it interpolated the enum value
into an English sentence (`Select a ${type.replace(/_/g,' ')}`) — a shape no
locale can translate — and is now a per-type key.

**Editing a rule silently dropped its recipient.** The picker resets the stored
id when `recipient_type` changes; it treated the edit form's `'' → 'user'`
hydration as such a change, so opening a saved rule blanked the recipient and
saving persisted the blank. Only a non-empty predecessor counts now.

**Building a filter submitted the surrounding form.** No `FilterBuilder` control
declared `type="button"`, and a bare <button> in a <form> defaults to submit —
adding a condition fired validation mid-edit and, on a valid form, saved.

**A rejected write showed the user raw server diagnostics.** The form rendered
`error.message` verbatim: `FORBIDDEN: insufficient privileges to update
showcase_private_note pi-TgoJ4_DM55Fqz` — untranslated, leaking the object's
machine name and the record id. Permission failures now render localized copy;
other failures keep the server's message, which is the useful part.

**The detail header offered "Edit" on records the user may only read.** Object
permissions can't say "this one record is read-only" — a read-only sharing grant
is exactly that — so the CTA opened the form and the user only found out at save
time. `RecordDetailView` and `DetailView` now also consult the explain engine's
record-grained verdict (`POST /security/explain` with a `recordId`, ADR-0090 D6
/ ADR-0095 C2 — the same pipeline the middleware runs). One cached request per
record, skipped when the object-level check already says no, and fail-open on
every uncertainty: a courtesy hint must never be why a permitted user can't act.
The server stays the authority (ADR-0057 D10).

Hardens `evaluatePermission` while there: a role config carrying only
`fieldPermissions` (no `actions`) made `check()` throw out of the render.

Browser-verified against the framework showcase Console in Chinese, end to end:
copy is localized, the picker lists real users / business units / positions, a
saved rule reopens intact, editing the filter no longer submits, and a
read-only recipient gets no Edit button while the same recipient at `edit`
level does — and can save.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…s-i18n-3821

# Conflicts:
#	packages/i18n/src/locales/ar.ts
#	packages/i18n/src/locales/de.ts
#	packages/i18n/src/locales/es.ts
#	packages/i18n/src/locales/fr.ts
#	packages/i18n/src/locales/ja.ts
#	packages/i18n/src/locales/ko.ts
#	packages/i18n/src/locales/pt.ts
#	packages/i18n/src/locales/ru.ts
@os-zhuang
os-zhuang merged commit 53642d4 into main Jul 28, 2026
15 checks passed
@os-zhuang
os-zhuang deleted the claude/sharing-widgets-i18n-3821 branch July 28, 2026 14:39
…unning past the border (objectstack#3821)

The sharing-rule object picker rendered "成员 (showcase_project_membership)"
straight through the control's right edge and into the field beside it.

Two causes, both mine from the picker work in this branch:

- `Combobox`'s trigger keeps the component's `w-[200px]` default, so the object
  picker sat at a third of the form column while `名称` next to it ran full
  width — and it is the field holding the longest content on the form.
- The selected label was a bare text child of a flex button. Flex items need
  `truncate` AND `min-w-0` to clip; it had neither, so the text simply drew
  outside the box instead of ellipsizing.

The label now lives in a truncating span, the trigger may shrink, and the
dropdown takes `--radix-popover-trigger-width` rather than its own hardcoded
200px — a combobox widened by its consumer used to clip its own options. The
two sharing-rule pickers pass `w-full` so they line up with every other input;
`w-[200px]` stays as the component default and consumers keep overriding it,
so no other call site changes.

Pinned by tests that assert the classes, since none of this is visible in an
accessibility tree. Browser-measured both ways: at 1280px the trigger is 362px
and the 247px label fits with room to spare; narrowed to 700px the label clips
at 210px with `text-overflow: ellipsis` and stays inside the button.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants