Uh oh!
There was an error while loading. Please reload this page.
feat(fields): select+multiple → multi-value chip picker; restore fields/core lint gates - #2709
Merged
os-zhuang merged 2 commits intoJul 20, 2026
Merged
Conversation
A `select` field/param declared `multiple: true` is a multi-value selector (spec allows `multiple` on select), but it rendered a single-value dropdown that could hold only one value. SelectField now delegates to the multi-value chip picker (the multiselect widget) when `multiple` is set, storing a string[]. Delegating inside SelectField — rather than at a type-resolution layer — means every surface that renders the `select` widget inherits multi-select identically: the object form, the inline grid editor, and the app-shell ActionParamDialog. No per-surface drift, no divergence from the form (which was the risk of special-casing it only in the param adapter). Single selects keep the cascading dropdown; multi + per-option visibleWhen cascading is not a combination in use today. Tests: SelectField renders chip toggles (not a combobox) when multiple, toggles values as an array, and renders badges in readonly mode. Docs: fields README "Multi-value selects". Changeset: @object-ui/fields minor. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rg7oavKSUdb2KzYEEYrCHX
…gates
The @object-ui/fields and @object-ui/core package lints were red at baseline,
so the gate could not catch NEW violations. Cleared all errors (behavior
unchanged):
fields (react-hooks/rules-of-hooks — hooks were called after early returns):
- ImageField: hoist the two useCallback handlers (+ their `images` dep) above
the readonly early return.
- TextAreaField: hoist the two useState calls above the readonly early return.
- index.tsx: useFieldTranslate no longer wraps useObjectTranslation in
try/catch (the hook is provider-safe and never throws; try/catch around a
hook is the violation). Mirrors useFieldLabel.
core:
- ExpressionEvaluator: preserve-caught-error — the package targets ES2020 whose
lib types only the 1-arg Error constructor, so `{ cause }` won't compile; the
original message is inlined and the rule is disabled with that justification.
- SafeExpressionParser: drop the useless `op` null-initializer (every branch
assigns or breaks); drop the useless `\-` escape in a char class.
- export-filename: drop the useless `\/` escape; the intentional control-char
range keeps a justified no-control-regex disable.
- record-title: `let display` -> `const` (never reassigned).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rg7oavKSUdb2KzYEEYrCHXThe latest updates on your projects. Learn more about Vercel for GitHub. |
Contributor
✅ Console Performance Budget
📦 Bundle Size Report
Size Limits
|
os-zhuang
marked this pull request as ready for review
July 20, 2026 02:14
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Jul 20, 2026
Merged
os-zhuang added a commit
that referenced
this pull request
Jul 20, 2026
…endsOn gating (#2715) (#2717) Closes the parity gap #2709 opened: when a `select` + `multiple` (and the `multiselect` type) began delegating to the multi-value chip picker, that picker did not implement the ADR-0058 per-option `visibleWhen` cascading / `dependsOn` gating the single `SelectField` has, so a multi-select with dependent options showed the unfiltered set. - Extract `useCascadingOptions` — the shared hook resolving per-option `visibleWhen` filtering, `dependsOn` gating, and the live `dependentValues` + predicate-scope wiring — and route both `SingleSelectField` and `MultiSelectField` through it (no duplicated resolver). - `MultiSelectField` narrows its offered chips against the live record + `current_user`, gates behind a "select the parent first" hint while a `dependsOn` field is empty, and surfaces a legible empty state. - Cascade-clear prunes only the now-invalid selections (array analogue of the single select's clear), keeping still-offered ones. - Tests mirror `SelectField.cascade.test.tsx`; README + changeset updated. Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This was referenced Jul 20, 2026
os-zhuang added a commit
that referenced
this pull request
Jul 20, 2026
#2730) Extends the fields/core lint-gate cleanup from #2709 to eight more packages whose lint was red at baseline on main — so the per-package `lint` gate could not catch new violations of the same class. Errors only; no behavior change. - no-useless-catch (data-objectstack): unwrap 5 rethrow-only try/catch blocks - preserve-caught-error (cli, data-objectstack, react): scoped disable w/ justification (ES2020 lib types the 1-arg Error only, per #2709 core case) - prefer-const (plugin-calendar, plugin-map) - no-empty-object-type (plugin-designer): extend-only interfaces -> type alias - no-useless-assignment (react): drop dead initializer - no-require-imports (plugin-calendar, plugin-timeline tests): async vi.mock factory with await import('react') - stale eslint-disable (plugin-markdown): drop unknown-rule react/no-danger directive, keep rationale as a plain comment Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
os-zhuang added a commit
that referenced
this pull request
Jul 20, 2026
…ui#2713 Wave 2) (#2737) Wave 2 of the #2713 lint-gate restoration (after #2730). Nine packages red at baseline on main; cleared every error, no behavior change (warnings out of scope). - rules-of-hooks (i18n, plugin-grid, plugin-view, plugin-list): translation helpers wrapped a provider-safe hook in try/catch -> unwrap (the #2709 fix); plugin-kanban moved its if(error) early return below the useCallback; collaboration __unsafe_usePresenceContext keeps its danger-prefix name via a justified scoped disable - static-components (layout, plugin-list, plugin-report): icon/registry lookups are stable refs -> scoped disable; plugin-charts TreemapCell (genuine inline component) hoisted to module scope - no-irregular-whitespace (plugin-grid ImportWizard): literal U+FEFF CSV BOM written as the escape (runtime-identical) - no-useless-assignment (plugin-grid BulkActionDialog): drop dead initializer - no-unsafe-function-type (plugin-view ViewTabBar): explicit callable signature - no-require-imports (plugin-kanban, plugin-view tests): async vi.mock + await import Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
os-zhuang added a commit
that referenced
this pull request
Jul 20, 2026
…#2713 Wave 3) (#2738) First package of Wave 3. plugin-dashboard was red at baseline on main; cleared every error, no behavior change (warnings out of scope). - rules-of-hooks (ObjectDataTable): unwrap try/catch around provider-safe useObjectTranslation (#2709 fix) - static-components (MetricCard, MetricWidget): getLazyIcon is a module-cached stable component per name -> scoped disable - no-irregular-whitespace (DatasetWidget): literal U+FEFF CSV BOM -> escape - no-useless-escape (recordFields): drop needless \$ in a character class - no-sparse-arrays (recordFields): [, ''] fallback -> [undefined, ''] - no-useless-assignment (PivotTable): suffix accumulator -> single const - no-require-imports (designMode test): async vi.mock + await import Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This was referenced Jul 20, 2026
os-zhuang added a commit
that referenced
this pull request
Jul 20, 2026
…713 Wave 3) (#2741) - useObjectChat (8 rules-of-hooks): the hook called different useCallbacks in each isApiMode return branch (both sets conditional). useChat is already unconditional; destructure its result + hoist all 8 callbacks above the branch so hook order is stable. Callback bodies unchanged (API `messages` -> apiMessages). - FloatingChatbotTrigger (rules-of-hooks): unwrap try/catch around provider-safe useObjectTranslation (#2709 fix) - shimmer (static-components): motion.create(Component) must key off the `as` prop; memoized per Component + justified scoped disable at the render Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <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 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.
Two follow-ups from the ADR-0059 next-steps list, as two clearly-separated commits.
1.
select+multiple→ multi-value chip picker (commit 1)A
selectfield/param declaredmultiple: trueis a multi-value selector (spec allowsmultipleonselect), but it rendered a single-value dropdown that could hold only one value.SelectFieldnow delegates to the multi-value chip picker (themultiselectwidget) whenmultipleis set, storing astring[].Why delegate inside
SelectField(not at a type-resolution layer): every surface that renders theselectwidget inherits multi-select identically — the object form, the inline grid editor, and the app-shellActionParamDialog— with no per-surface drift. (Special-casing it only in the param adapter would have diverged params from the form, which is exactly the trap called out when this was first scoped.) Single selects keep the cascading dropdown; multi + per-optionvisibleWhencascading is not a combination in use today (noted in code + README).multiple, toggle as an array, badges in readonly.aria-pressedfalse→true on click. Screenshot confirms filled Red+Green chips inside the dialog. Throwaway harness removed after.2. Restore
@object-ui/fields+@object-ui/corelint gates (commit 2)Both package lints were red at baseline on
main, so the gate could not catch new violations. Cleared every error (no behavior change):react-hooks/rules-of-hooks:ImageField/TextAreaFieldhoist their hooks above thereadonlyearly return;index.tsxuseFieldTranslateno longer wraps the provider-safeuseObjectTranslationin try/catch (wrapping a hook is the violation).no-useless-assignment/no-useless-escape(×2) /prefer-constfixed directly;no-control-regex(intentional filename control-char range) andpreserve-caught-error(package targets ES2020 whose lib types only the 1-argError, so{ cause }won't compile — message is inlined) carry justified scoped disables.Checks
@object-ui/fields+@object-ui/core: lint clean (0 errors) and type-check green — the gates now pass, having been red before.fields+core(76 files / 1436) and the select-consuming suitesapp-shell+components+plugin-form+plugin-grid(297 files / 2435) all green.Changeset:
@object-ui/fieldsminor,@object-ui/corepatch.🤖 Generated with Claude Code
https://claude.ai/code/session_01Rg7oavKSUdb2KzYEEYrCHX
Generated by Claude Code