fix(fields,core,detail): make the sharing-rule dialog usable — i18n, a picker that lists people, and permission-aware CTAs - #2920
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
…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
…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>
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 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
$orderby: 'name asc'fell into theObject.entriesbranch ofqueryParamsToRecordand was walked character by character into0 n,1 a,2 m,…; the server sorted by columns that don't exist and returned nothinguseFieldTranslation'' → 'user'hydration as a type switch, and the blank was then persisted on saveFilterBuildercontrol settype="button"; a bare<button>in a<form>defaults to submitFORBIDDEN: insufficient privileges to update showcase_private_note pi-TgoJ4_DM55Fqzerror.messageverbatim — untranslated, and leaking the object machine name and record idObjectGridbuilds the same string$orderbyfrom a schema-levelsortin three places, so this also fixes grids that silently rendered an empty table.Notable design points
`Select a ${type.replace(/_/g,' ')}`is a shape no locale can translate.POST /api/v1/security/explainwith arecordId, 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.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).explainrequest 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:
editlevel: Edit present, save succeeds; the user's own records keep Edit throughoutTests:
plugin-detail+permissions303 ✓,components319 ✓,fields/core/i18nsuites ✓. Each new test verified to fail without its fix. Two known-unrelated flakes on this machine: a TZ-dependentDATEFORMATcase that also fails onmain, andReportPreview.datasettiming out only under full-suite load (passes in isolation on both branches).🤖 Generated with Claude Code