Skip to content

Feat/slot field factory#9

Open
wilcorrea wants to merge 2 commits intomainfrom
feat/slot-field-factory
Open

Feat/slot field factory#9
wilcorrea wants to merge 2 commits intomainfrom
feat/slot-field-factory

Conversation

@wilcorrea
Copy link
Contributor

@wilcorrea wilcorrea commented Mar 8, 2026

Summary by CodeRabbit

Release Notes

  • New Features

    • Added component registry system for UI customization (custom inputs, buttons, empty states).
    • Enhanced table with improved pagination controls, column selection, and customizable empty states.
    • Added destructive action confirmations with specialized dialog titles.
    • Redesigned list field UI with table-based management and dialog forms.
    • Introduced page layout options (flat, bare) and header action support.
    • Expanded field localization with placeholder and description support.
  • Improvements

    • Enhanced Portuguese translations with new error messages.
    • Refined dialog and form interactions for better UX.

# Conflicts:
#	packages/core/src/fields/list.ts
#	packages/react-web/src/renderers/ListField.tsx
@coderabbitai
Copy link

coderabbitai bot commented Mar 8, 2026

📝 Walkthrough

Walkthrough

This pull request refactors the data hydration system from a bootstrap-centric approach to a fetch-based hydration pattern with discriminated union types, introduces a component registry for dynamic component resolution, adds page actions context and layout management, expands i18n helpers, and updates hook implementations across React, Svelte, and Vue packages to support the new pattern.

Changes

Cohort / File(s) Summary
Core Type System
packages/core/src/types.ts, packages/core/src/schema.ts
Introduces FetchType enum with 'record' and 'collection' values; converts HookFetchContext to discriminated union; updates hook signatures to use hydrate callbacks instead of returning data directly; extends DialogContract, NavigatorContract, and FormContract with new methods.
Field Definitions
packages/core/src/fields/base.ts, packages/core/src/fields/list.ts
Replaces structuredClone with cloneWithFunctions helper to preserve functions during config serialization; updates ListFieldDefinition.itemSchema signature to use SchemaProvide; adds fluent API methods (events, hooks, handlers) to ListFieldDefinition.
Localization
packages/core/src/locales/pt-BR.ts
Adds Portuguese-Brazilian translations for create, destroy, and update error messages; extends dialog configuration with confirmDestructiveTitle.
Core Exports & Mock
packages/core/src/index.ts, packages/core/src/mock.ts
Re-exports FetchType and FetchTypeValue from types; adds navigator.open() and form.setErrors() mock functions.
React Web Component Registry
packages/react-web/src/components/registry.ts, packages/react-web/src/index.ts
Introduces ComponentRegistry interface with strongly-typed component mapping; exports registerComponents and getComponent functions for runtime component customization.
Page Layout & Actions System
packages/react-web/src/components/PageActionsContext.tsx, packages/react-web/src/components/DataPage.tsx
Adds PageActionsContext with usePageActions and usePageActionsState hooks for header action registration; refactors DataPage to support bare and flat layout modes with optional header configuration.
Dialog & Form Components
packages/react-web/src/components/Dialog.tsx, packages/react-web/src/components/Form.tsx
Extends Dialog to support destructive flag in confirm options with custom title rendering; updates Form to resolve components dynamically via registry and integrate page-level header actions via PageActionsContext.
Data Table Refactoring
packages/react-web/src/components/Table.tsx
Expands DataTableProps with selectable, actionsPosition, showColumnSelector, showTopActions, searchSlot, emptyState, and value properties; refactors to use dynamic component resolution via registry; adds row selection, header actions, and enhanced pagination with numbered page buttons.
Table Utilities
packages/react-web/src/components/defaults/Pagination.tsx, packages/react-web/src/components/defaults/ColumnSelector.tsx, packages/react-web/src/components/defaults/EmptyState.tsx, packages/react-web/src/components/defaults/DebugPanel.tsx
Pagination now displays numbered page buttons with sliding window calculation; ColumnSelector and EmptyState components updated with dynamic label and icon resolution; EmptyState now accepts props for customization; DebugPanel introduces collapsible entries via collapsed flag.
Internationalization Helpers
packages/react-web/src/support/i18n.ts
Introduces resolveFieldLabel, resolveFieldOption, resolveFieldPlaceholder, and resolveFieldDescription helpers for centralized i18n resolution across field components.
Field Renderers
packages/react-web/src/renderers/TextField.tsx, packages/react-web/src/renderers/TextareaField.tsx, packages/react-web/src/renderers/NumberField.tsx, packages/react-web/src/renderers/SelectField.tsx, packages/react-web/src/renderers/DateField.tsx, packages/react-web/src/renderers/TimeField.tsx, packages/react-web/src/renderers/ToggleField.tsx, packages/react-web/src/renderers/MultiSelectField.tsx
All field renderers updated to use centralized i18n resolution helpers; SelectField and TextField/TextareaField added dynamic component resolution via registry with fallbacks to native elements; DateField and TimeField updated to use resolveFieldLabel.
List Field Restructuring
packages/react-web/src/renderers/ListField.tsx, packages/react-web/src/renderers/list/useListDialog.ts, packages/react-web/src/renderers/list/useListComponent.ts
Replaces modal-based item editing with DataTable-driven list and portal-based DataForm; introduces useListDialog for dialog state management and useListComponent for building ComponentContract with navigation and dialog integration; adds DEFAULT_SCOPES constant for scope-to-route mapping.
Theme & Types
packages/react-web/src/theme/default.ts, packages/react-web/src/types.ts
Updates Theme interface from const-asserted to satisfies-based constraint; adds EmptyStateProps, EmptyStateAction, DataTableEmptyStateInput, SearchBarProps, TextInputProps, TextareaInputProps, SelectInputProps, DialogButtonProps interfaces.
Component Contract
packages/react-web/src/contracts/component.ts
Adds navigator.open(route, params) method to ComponentContract for opening routes via ScopeRoute objects.
Support Icons
packages/react-web/src/support/Icon.tsx
Adds "square-pen" icon path to Icon component's paths map.
React Hook Implementations
packages/react/src/types.ts, packages/react/src/use-data-form.ts, packages/react/src/use-data-table.ts
Updates FetchHookFn to return void | Promise<void>; refactors useDataForm and useDataTable to support fetch-based hydration with hydrate callbacks; adds value and context propagation; implements two-phase orchestration (fetch hydrate → bootstrap).
Svelte Hook Implementations
packages/svelte/src/types.ts, packages/svelte/src/use-data-form.ts, packages/svelte/src/use-data-table.ts
Matches React pattern: FetchHookFn signature updated to void | Promise<void>; useDataForm and useDataTable refactored with fetch-based hydration, context support, and two-phase orchestration.
Vue Hook Implementations
packages/vue/src/types.ts, packages/vue/src/use-data-form.ts, packages/vue/src/use-data-table.ts
Consistent updates: FetchHookFn signature changed; useDataForm and useDataTable refactored for fetch-based hydration with setErrors exposure and bootstrap sequencing.
Hook Tests
playground/*/tests/src/domain/person/hooks.test.ts
Updates hook tests across React Native, React Web, Svelte, and Vue playgrounds to verify new fetch-based hydration pattern; tests now assert hydrate invocation instead of direct data returns; bootstrap scope coverage reduced to view-only for field disabling verification.
Demo Hooks
packages/demo/src/settings/hooks.ts
Refactors createDefault hook implementation to split bootstrap (field disabling) and fetch (data hydration) responsibilities by scope; introduces per-scope fetch handlers for view, edit, and index with type discrimination and hydrate callbacks.

Sequence Diagram(s)

sequenceDiagram
    participant Component
    participant useDataForm
    participant FetchHook
    participant Service
    participant BootstrapHook

    Component->>useDataForm: mount with schema & hooks
    useDataForm->>useDataForm: initialize loading state
    
    alt Has FetchHook
        useDataForm->>FetchHook: call with FetchHookContext<br/>{type, context, params, hydrate, component}
        FetchHook->>Service: read/paginate data
        Service-->>FetchHook: return data
        FetchHook->>useDataForm: hydrate(data) callback
        useDataForm->>useDataForm: update state & rows
    end
    
    alt Has BootstrapHook (after fetch)
        useDataForm->>BootstrapHook: call with BootstrapHookContext
        BootstrapHook->>useDataForm: modify schema overrides
        useDataForm->>useDataForm: merge & apply overrides
    end
    
    useDataForm->>useDataForm: set loading = false
    useDataForm-->>Component: return form & data
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • devitools/ybyra#6: Modifies ListFieldDefinition.itemSchema handling to store SchemaProvide and changes how schema is passed through list field configuration.

Poem

🐰 Hops through the hoops of hydrate callbacks,
Fetch brings the data, bootstrap patches,
Components register, icons align,
List fields dance in table's design,
I18n whispers translations so fine! 🌱

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Feat/slot field factory' is vague and does not clearly convey the actual changes in this comprehensive PR that includes hook refactoring, component registry, layout improvements, and multiple UI enhancements. Use a more descriptive title that highlights the primary changes, such as 'Refactor hooks architecture and introduce component registry' or 'Restructure data hydration with hooks and add component customization'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/slot-field-factory

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 15

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
packages/svelte/src/use-data-form.ts (2)

71-72: ⚠️ Potential issue | 🟠 Major

Update the dirty baseline before publishing hydrated state.

dirty only subscribes to state. With the current order, state.set(currentState) recomputes against the old initialState, and the later Object.assign(initialState, currentState) does not trigger another recompute. Hydrated forms will stay dirty until the next state change.

Suggested fix
-        state.set(currentState)
-        Object.assign(initialState, currentState)
+        Object.assign(initialState, currentState)
+        state.set(currentState)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/svelte/src/use-data-form.ts` around lines 71 - 72, The dirty
baseline is updated after publishing the hydrated state, so the dirty subscriber
recomputes against the old initialState; swap the operations so the baseline is
updated first: call Object.assign(initialState, currentState) before invoking
state.set(currentState) (or otherwise ensure initialState is updated prior to
state.set) so the dirty computation uses the new baseline when the state is
published.

38-91: ⚠️ Potential issue | 🟠 Major

Always clear loading and handle hook failures explicitly.

If either hook rejects, execution skips Line 88 and run() still bubbles an unhandled rejection from Line 91. The form can stay stuck in loading=true on any fetch/bootstrap failure. Wrap the orchestration in try/finally and add an explicit rejection path for run().

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/svelte/src/use-data-form.ts` around lines 38 - 91, The run function
can leave loading=true when fetchHook or bootstrapHook reject and currently its
unhandled rejection bubbles from the run() call; wrap the orchestration inside
run in a try/finally so loading.set(false) always runs (and still set
fieldOverrides/state inside the try), and add an explicit catch inside run to
rethrow or return a rejected Promise with the original error (so callers can
handle it). Specifically modify run (and its invocation run()) to: surround the
existing fetchHook/ events/ bootstrapHook logic with try { ... } finally {
loading.set(false) }, and in the try add a catch block that logs or wraps the
error then rethrows; ensure run returns the Promise so the external
run().catch(...) can handle failures. Ensure you still update
fieldOverrides/state inside the try before finally runs.
packages/react/src/use-data-form.ts (1)

278-304: ⚠️ Potential issue | 🟠 Major

Potential stale closures in action execute callbacks.

The actions memo captures errors, dirty, valid, reset, and validate inside the execute function, but these are not included in the dependency array. This can cause actions to operate on stale form state.

🔧 Proposed fix to include missing dependencies
-  }, [schema.actions, scope, handlers, state, component, permissions]);
+  }, [schema.actions, scope, handlers, state, component, permissions, errors, dirty, valid, reset, validate]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react/src/use-data-form.ts` around lines 278 - 304, The memoized
actions' execute closures capture form helpers and state (errors, dirty, valid,
reset, validate, setErrors, valueRef) but the useMemo dependency array only
lists [schema.actions, scope, handlers, state, component, permissions], causing
stale closures; update the dependency array for the useMemo that produces
actions to include errors, dirty, valid, reset, validate, setErrors, and
valueRef.current (or valueRef) so execute uses fresh references, or
alternatively avoid closing over mutable values by reading from refs/handlers at
call-time (e.g., use valueRef.current and handlers[name] inside execute) and
ensure all referenced symbols (actions, execute, handlers, state, component,
errors, dirty, valid, reset, validate, setErrors, valueRef) are reflected in
dependencies.
🧹 Nitpick comments (15)
packages/svelte/src/use-data-table.ts (1)

94-116: Hydration pattern looks solid.

The stale-request guard via fetchId and the Promise.resolve wrapper to handle both sync/async hooks are well-implemented.

Consider adding a .catch() to surface fetch errors to the consumer. Currently, if fetchHook rejects, the error is silently swallowed and users cannot distinguish between an empty result and a failed request.

♻️ Optional: Add error state handling
+  const error = writable<Error | null>(null)
+
   function reload () {
     const id = ++fetchId
     const fetchHook = hooks?.fetch?.[scope]
     if (!fetchHook) return

     loading.set(true)
+    error.set(null)
     const hydrate = (result: { data: Record<string, unknown>[]; total: number }) => {
       if (fetchId !== id) return
       rows.set(result.data)
       total.set(result.total)
     }
     Promise.resolve(
       fetchHook({
         type: 'collection',
         context: context ?? {},
         params: {
           page: get(page),
           limit: get(limit),
           sort: get(sortField),
           order: get(sortOrder),
           filters: get(filters),
         },
         hydrate,
         component,
       }),
-    ).finally(() => {
+    ).catch((err) => {
+      if (fetchId !== id) return
+      error.set(err instanceof Error ? err : new Error(String(err)))
+    }).finally(() => {
       if (fetchId !== id) return
       loading.set(false)
     })
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/svelte/src/use-data-table.ts` around lines 94 - 116, The
Promise.resolve wrapper around fetchHook currently swallows rejections; add
error propagation and state handling by attaching a .catch handler to the
Promise.resolve(fetchHook(...)) call that (1) checks the stale-request guard
(fetchId !== id) before acting, (2) sets loading.set(false) as needed, and (3)
surfaces the error to the consumer (e.g., call an onError callback or set an
error store/state) so callers of useDataTable can distinguish failures from
empty results; update references to hydrate, fetchHook, fetchId, id, and loading
(and optionally rows/total) when implementing this error path.
packages/demo/src/settings/hooks.ts (1)

13-26: Consider extracting duplicate fetch logic for Scope.view and Scope.edit.

The fetch handlers for Scope.view and Scope.edit are identical. This duplication can be reduced by extracting a shared helper.

♻️ Proposed refactor to reduce duplication
+async function fetchRecord(context: FetchHookContext, service: ServiceContract) {
+  if (context.type !== FetchType.record || !context.context.id) {
+    return
+  }
+  const data = await service.read(context.context.id as string)
+  context.hydrate(data)
+}
+
 export function createDefault (service: ServiceContract) {
   return {
     bootstrap: {
       async [Scope.view] ({ schema }: BootstrapHookContext) {
         for (const field of Object.values(schema)) {
           field.disabled = true
         }
       },
     },
     fetch: {
-      async [Scope.view] (context: FetchHookContext) {
-        if (context.type !== FetchType.record || !context.context.id) {
-          return
-        }
-        const data = await service.read(context.context.id as string)
-        context.hydrate(data)
-      },
-      async [Scope.edit] (context: FetchHookContext) {
-        if (context.type !== FetchType.record || !context.context.id) {
-          return
-        }
-        const data = await service.read(context.context.id as string)
-        context.hydrate(data)
-      },
+      [Scope.view]: (ctx: FetchHookContext) => fetchRecord(ctx, service),
+      [Scope.edit]: (ctx: FetchHookContext) => fetchRecord(ctx, service),
       async [Scope.index] (context: FetchHookContext) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/demo/src/settings/hooks.ts` around lines 13 - 26, The handlers for
Scope.view and Scope.edit duplicate the same fetch/hydrate logic; extract a
shared async helper (e.g., fetchAndHydrateRecord(context: FetchHookContext))
that performs the early return when context.type !== FetchType.record or missing
context.context.id, calls service.read(context.context.id as string), then calls
context.hydrate(data), and invoke that helper from both the [Scope.view] and
[Scope.edit] handlers so behavior and types for FetchHookContext, FetchType,
service.read and context.hydrate are preserved.
packages/core/src/types.ts (1)

210-217: Consider whether PaginateParams is appropriate for FetchHookContext with type: 'record'.

For a single record fetch, including PaginateParams (with page, limit, sort, order, filters) seems semantically misaligned. Currently, callers pass placeholder values like { page: 1, limit: 1 } which adds noise.

Consider introducing a separate params type for record fetches (e.g., just context with the record identifier) or documenting why the unified params shape is intentional.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/types.ts` around lines 210 - 217, FetchHookContext
currently uses PaginateParams for the 'record' variant, which is semantically
wrong and forces callers to pass placeholder pagination values; change the type
by replacing PaginateParams in the 'record' branch with a new minimal params
type (e.g., RecordParams or RecordFetchParams) that contains only the record
identifier(s) needed by your API (or make params optional/absent if the
identifier lives in context), update the FetchHookContext declaration to
reference this new type for the branch where type: 'record', and adjust any
usages of FetchHookContext.fetch/hydrate, component, or callers to pass the new
smaller params shape (or remove dummy {page, limit} values) so callers no longer
need to supply pagination for single-record fetches.
packages/core/src/fields/base.ts (1)

101-110: Line 103 is unreachable dead code.

The check typeof value === 'function' on line 103 will never execute because line 102 already returns early for functions. Since typeof someFunction !== 'object' is true, functions are returned on line 102.

♻️ Proposed simplification
 function cloneWithFunctions(value: unknown): unknown {
   if (value === null || typeof value !== 'object') return value
-  if (typeof value === 'function') return value
   if (Array.isArray(value)) return value.map(cloneWithFunctions)
   const result: Record<string, unknown> = {}
   for (const key of Object.keys(value as object)) {
     const v = (value as Record<string, unknown>)[key]
     result[key] = typeof v === 'function' ? v : cloneWithFunctions(v)
   }
   return result
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/fields/base.ts` around lines 101 - 110, In
cloneWithFunctions, the check "if (typeof value === 'function')" is unreachable
because the prior "if (value === null || typeof value !== 'object') return
value" already returns for functions; remove the redundant function-type branch
from cloneWithFunctions and keep the early return for non-objects/null, the
Array.isArray(value) branch, and the recursive object-cloning loop so functions
remain returned as-is while objects/arrays are cloned.
packages/vue/src/use-data-form.ts (1)

35-35: Comments are in Portuguese; consider using English for consistency.

The inline comments on lines 35, 47, and 68 are in Portuguese ("hidrata os dados", "aplica hydrate e dispara events", "orquestra estado do schema"). For codebase consistency and international collaboration, English comments are typically preferred.

♻️ Suggested English translations
-      // 1. fetch → hidrata os dados
+      // 1. fetch → hydrate data
       if (fetchHook) {
-      // 2. aplica hydrate e dispara events
+      // 2. apply hydration and fire events
       if (hydratedData) {
-      // 3. bootstrap → orquestra estado do schema (roda após hydrate)
+      // 3. bootstrap → orchestrate schema state (runs after hydrate)
       if (bootstrapHook) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/vue/src/use-data-form.ts` at line 35, Replace the three Portuguese
inline comments in use-data-form.ts with English equivalents for consistency:
change the comment at the start of the data fetch block ("hidrata os dados") to
"fetch → hydrate the data", the comment near the hydrate/events application
("aplica hydrate e dispara events") to "apply hydrate and emit events", and the
comment around schema orchestration ("orquestra estado do schema") to
"orchestrate schema state"; update the comments adjacent to the functions/blocks
that perform these tasks (e.g., the fetch/hydrate block, the hydrate+events
handler, and the schema orchestration logic) so the intent is clear in English.
packages/core/src/fields/list.ts (1)

30-43: Consider stronger typing for fluent method parameters.

The new events(), hooks(), and handlers() methods accept Record<string, unknown>, which is quite loose. If these correspond to specific contract types (e.g., SchemaEvents, SchemaHooks, SchemaHandlers), consider using those types to provide better type safety and IDE autocompletion for consumers.

💡 Example with stricter typing
- events(events: Record<string, unknown>): this {
+ events(events: SchemaEvents<any>): this {
    this._config.attrs = { ...this._config.attrs, events }
    return this
  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/fields/list.ts` around lines 30 - 43, The methods events(),
hooks(), and handlers() currently accept the loose Record<string, unknown>;
replace those parameter types with the specific contract types (e.g.,
SchemaEvents, SchemaHooks, SchemaHandlers) used by your schema/field system so
consumers get stronger typing and autocomplete; update the function signatures
for events(), hooks(), and handlers() to use those types, adjust imports or
generics where needed, and ensure assignments to this._config.attrs remain
compatible (adding casts only if absolutely necessary).
packages/react-web/src/renderers/list/useListComponent.ts (1)

13-18: Consider clarifying inferScope behavior for parameterized routes.

inferScope compares paths using strict equality (path === route.path), which means it will match literal strings like 'add' or ':id' but not actual ID values like '123' or 'abc-456/edit'.

Based on the usage in handleNavigation, it appears this is called with route constants rather than actual URLs, which makes this behavior correct. If this hook is ever used with actual path segments, the matching logic would need adjustment.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react-web/src/renderers/list/useListComponent.ts` around lines 13 -
18, The inferScope function currently uses strict equality between the incoming
path and DEFAULT_SCOPES' route.path which fails for concrete parameterized
segments (e.g., '123' vs ':id'); update this by either (A) changing inferScope
to match parameterized routes (e.g., normalize/match route.path patterns like
':id' or '/:id/edit' against actual path segments) or (B) explicitly
document/guard its intended use with route constants only—add a short comment
above inferScope and, if used in handleNavigation, ensure callers pass route
constants (not raw URLs) or perform normalization before calling inferScope;
reference function name inferScope and DEFAULT_SCOPES and the consumer
handleNavigation when making the change.
packages/react-web/src/types.ts (1)

79-79: Consider using English for JSDoc/inline comments.

The comment describing DataTableEmptyStateInput is in Portuguese. For consistency with the rest of the codebase, use English.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react-web/src/types.ts` at line 79, Update the JSDoc comment for the
DataTableEmptyStateInput type to English to match the codebase language; locate
the comment currently in Portuguese above DataTableEmptyStateInput and replace
it with an English equivalent describing the props passed by the consumer and
that `action` accepts a name or an ActionConfig from the schema.
packages/react/src/use-data-form.ts (1)

39-40: Consider using English for code comments.

Comments like "fetch → hidrata os dados", "aplica hydrate e dispara events", and "bootstrap → orquestra estado do schema" should be in English for consistency.

Also applies to: 51-51, 72-72

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react/src/use-data-form.ts` around lines 39 - 40, Replace the
Portuguese inline comments in packages/react/src/use-data-form.ts with clear
English equivalents to keep code consistent; specifically update the comments
near the fetchHook block ("fetch → hidrata os dados"), the comment around the
hydrate/events application ("aplica hydrate e dispara events"), and the
bootstrap/state-orchestration comment ("bootstrap → orquestra estado do schema")
to concise English phrases (e.g., "fetch → hydrate data", "apply hydrate and
emit events", "bootstrap → orchestrate schema state") while leaving surrounding
logic (fetchHook, hydrate, event dispatching, bootstrap/orchestration code
paths) unchanged.
packages/react-web/src/components/defaults/Pagination.tsx (1)

35-35: Consider using English for code comments.

The inline comments are in Portuguese. For consistency and broader maintainability, consider using English.

Also applies to: 65-65

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react-web/src/components/defaults/Pagination.tsx` at line 35,
Replace the Portuguese inline comments in the Pagination component with English
equivalents to improve consistency and maintainability: locate the comments in
the Pagination.tsx component (e.g., the comment reading "Esquerda: Itens por
página" and the other comment noted by the reviewer) and update them to concise
English descriptions (for example "Left: Items per page" or similar) preserving
meaning and context near the related JSX elements or props.
packages/react-web/src/renderers/SelectField.tsx (1)

24-24: Consider memoizing getComponent result if registry lookups are expensive.

getComponent('SelectInput') is called on every render. If the registry lookup has any overhead, consider using useMemo or ensuring getComponent is internally memoized.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react-web/src/renderers/SelectField.tsx` at line 24, The code calls
getComponent('SelectInput') on every render which can be expensive; change
CustomSelect to be obtained once by memoizing the lookup — e.g., wrap
getComponent('SelectInput') in a React useMemo or ensure getComponent caches
internally — so replace the repeated call that assigns const CustomSelect =
getComponent('SelectInput') with a memoized result tied to no or appropriate
dependencies (referencing CustomSelect and getComponent('SelectInput') to locate
the site).
packages/react-web/src/components/Table.tsx (1)

405-411: Inconsistent use of Position enum vs string literal.

Line 353 uses Position.header but line 406 uses the string 'top'. Use Position.top for consistency and type safety.

Proposed fix
           {showTopActions && table.actions
-              .filter((a) => a.config.positions.includes('top'))
+              .filter((a) => a.config.positions.includes(Position.top))
              .map((a) => <ResolvedActionButton
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react-web/src/components/Table.tsx` around lines 405 - 411, The code
mixes string literals and the Position enum for action positions; replace the
string 'top' used in the filter at the showTopActions render with the enum value
Position.top to match usage like Position.header and preserve type safety —
update the filter on table.actions (the .filter((a) =>
a.config.positions.includes('top')) expression) to use Position.top and ensure
Position is imported/available where ResolvedActionButton is rendered.
packages/react-web/src/renderers/ListField.tsx (3)

68-71: Unused refs rawValueRef and dialogStateRef.

These refs are created and updated but do not appear to be used within this component. If they're intended for use by child components or hooks, consider passing them explicitly; otherwise, remove to reduce noise.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react-web/src/renderers/ListField.tsx` around lines 68 - 71, The
refs rawValueRef and dialogStateRef in ListField are created and updated but
never used; either remove the unused declarations and their assignments (delete
const rawValueRef = useRef(rawValue); rawValueRef.current = rawValue; const
dialogStateRef = useRef(dialogState); dialogStateRef.current = dialogState) or,
if they were meant to be consumed by children or hooks, explicitly pass those
refs into the child components or hooks that need them (e.g., as props or via
context) so usage is visible; update any related code in the ListField component
to reflect the removal or the new prop/context usage.

94-117: Modal lacks an explicit close button for accessibility.

The modal can only be closed by clicking the overlay backdrop. For better accessibility (keyboard navigation, screen readers), consider adding an explicit close button inside the modal with proper ARIA attributes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react-web/src/renderers/ListField.tsx` around lines 94 - 117, The
modal markup rendered by createPortal lacks an explicit close control; add a
visible, focusable close button inside the inner modal div (the element with
style={styles.modal}) that calls closeDialog on click, includes an accessible
label (e.g., aria-label="Close" or aria-label="Close dialog") and keyboard
support (focusable via Tab and triggers close on Enter/Space), and ensure it is
reachable by screen readers near the DataForm; optionally hook an onKeyDown for
Escape to call closeDialog if not already handled elsewhere.

32-47: Reactive array wrapper may cause unexpected behavior.

The reactiveValue memo creates a custom array with overridden push and splice methods. However:

  1. The splice signature diverges from the native Array—when deleteCount is omitted, it should default to removing all elements from start to the end, but here deleteCount ?? next.length removes next.length elements which could exceed bounds.
  2. Returning next.length from push and removed from splice reflects the local computation, but onChangeRef.current(next) triggers an async state update—callers expecting the array to be mutated in place will be surprised.

If callers rely on standard Array semantics, consider documenting this behavior or using a proxy pattern that better mimics native semantics.

Potential fix for splice default
     arr.splice = (start: number, deleteCount?: number, ...items: Record<string, unknown>[]) => {
       const next = [...rawValue]
-      const removed = next.splice(start, deleteCount ?? next.length, ...items)
+      const removed = next.splice(start, deleteCount ?? (next.length - start), ...items)
       onChangeRef.current(next)
       return removed
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react-web/src/renderers/ListField.tsx` around lines 32 - 47, The
reactive array wrapper overrides Array.push and Array.splice but misimplements
splice default and can surprise callers because mutations aren’t in-place;
change splice’s default deleteCount to (next.length - start) so removing until
end behaves like native (use deleteCount ?? (next.length - start)), and update
push/splice behavior to more closely mimic native semantics by either (a)
applying mutations to the proxy/returned array synchronously before calling
onChangeRef.current or (b) replace this pattern with a Proxy that intercepts
mutations and forwards them to onChangeRef.current while reflecting changes
immediately; locate and modify the reactiveValue creation and its push/splice
implementations (and keep onChangeRef.current(next) calls) to ensure return
values and in-place mutation semantics match native Array expectations.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/react-web/src/components/DataPage.tsx`:
- Around line 49-67: The DataPage component currently early-returns full-screen
loading/forbidden UIs (using loading, permitted, forbidden and
styles.loading/styles.forbidden), which breaks host-controlled layouts for pages
rendered with layout="flat" or bare mode; change the render logic to avoid early
returns so flat/bare pages are not replaced with a 100vh wrapper: detect
flat/bare (props.layout === 'flat' or props.bare) and for those cases render
inline/smaller loading or forbidden placeholders (reuse Icon and
styles.forbiddenText but remove minHeight: '100vh' and the full-screen
container) or render the existing children plus a lightweight overlay; otherwise
keep the current full-screen behavior for non-flat pages. Ensure you update the
conditional branches around loading/permitted/forbidden in DataPage to return
the children/layout path for flat/bare while showing a non-fullscreen indicator.
- Around line 74-84: The current DataPage uses resolvedHeaderEnd = headerEnd ??
registeredActions which drops registeredActions when headerEnd is provided;
instead compose both so nested-registered actions are not hidden: in the
DataPage render (look for headerEnd, registeredActions, resolvedHeaderEnd)
replace the nullish-coalescing behavior with a composed fragment/element that
renders registeredActions and headerEnd together (preserving desired order and
spacing) and use that composed value where resolvedHeaderEnd is used so both
sets of controls show.

In `@packages/react-web/src/components/defaults/Pagination.tsx`:
- Around line 9-14: The getVisiblePages function can produce fewer than 5 pages
near the end because start isn't re-adjusted after end is clamped; update
getVisiblePages to ensure a consistent 5-item sliding window by computing end =
Math.min(total, start + 4) and then, if (end - start + 1) < 5, shift start =
Math.max(1, end - 4) (or equivalently compute start = Math.max(1,
Math.min(current - 2, total - 4))) so the returned Array.from(...) always yields
up to 5 page numbers when total >= 5.

In `@packages/react-web/src/components/Form.tsx`:
- Around line 36-50: The effect in useLayoutEffect is stale because it only
depends on pageActions and props.schema.domain and never cleans up the
registered node; update the dependency array to include form.actions (or the
derived headerActions) so the effect re-runs when form.actions changes, remove
the eslint-disable comment, and add a cleanup that removes the previously
registered element—capture the return value from pageActions.register (or the
DOM node you passed) and call the corresponding unregister/remove API in the
cleanup function so the header buttons are deregistered when headerActions
becomes empty or on unmount; target symbols: useLayoutEffect,
pageActions.register, form.actions, headerActions, ResolvedActionButton.

In `@packages/react-web/src/components/Table.tsx`:
- Around line 332-347: The current lookup that finds a resolved action via
table.actions.find comparing config.variant, config.positions.join(), and
config.order is fragile (in the block that casts action as ActionConfig and
assigns const resolved). Change the matching to use a unique
identifier—preferably the action name (e.g., match a.config.name ===
config.name) or a dedicated unique id field on ActionConfig/a.config—so resolved
is selected deterministically; update any callers/fixtures to populate that
identifier and keep the existing fallback (return { ...rest }) if no match is
found.
- Around line 350-367: The useLayoutEffect is missing dependencies so
headerActions can become stale; update the dependency array to include
table.actions (or table if actions isn't stable) and the ResolvedActionButton
component reference so the effect re-runs when actions or the component change,
e.g., replace [pageActions, domain] with [pageActions, domain, table.actions,
ResolvedActionButton] (or [pageActions, domain, table] if actions is accessed
via table), and remove the eslint-disable comment; keep the logic that computes
headerActions and calls pageActions.register unchanged.
- Line 312: The search state (const [search, setSearch]) is never applied to the
table, so typing in ResolvedSearchBar doesn't filter results; hook the state up
to the table by calling the table filtering API whenever search changes (e.g.,
in a useEffect call table.setGlobalFilter(search) or
table.setFilter('<yourColumnId>', search) depending on whether you use a global
filter or a specific column filter). Ensure the effect runs on mount and when
search updates and that clearing search clears the table filter; keep setSearch
passed into ResolvedSearchBar as-is.

In `@packages/react-web/src/renderers/ListField.tsx`:
- Around line 55-61: The hook call uses a non-null assertion on schema (schema!)
before the component returns early when schema may be undefined; move the
useListComponent(...) invocation to after the existing early-return check (if
(!schema || proxy.hidden) return ...) so it only runs when schema is defined, or
alternatively remove the non-null assertion and update useListComponent’s
call/site types to accept SchemaProvide | undefined and handle undefined inside;
update references to tableComponent accordingly (useListComponent, schema,
tableComponent).

In `@packages/react-web/src/renderers/TextareaField.tsx`:
- Around line 97-102: The description style in TextareaField.tsx currently sets
both marginTop and margin: 0, which neutralizes the intended spacing; update the
description style (the description key in the style/object) to remove the
blanket margin: 0 so the marginTop: theme.spacing.xs takes effect
(alternatively, replace margin: 0 with a more specific shorthand like
marginBottom: 0 if you need to reset only the bottom margin while preserving
marginTop).

In `@packages/react-web/src/renderers/TextField.tsx`:
- Around line 96-101: The description style in TextField.tsx mistakenly uses
"margin: 0" which overrides "marginTop: theme.spacing.xs"; remove the "margin:
0" property (or replace it with an explicit "marginBottom" /
"marginLeft"/"marginRight" if needed) inside the description style object so
that description keeps marginTop: theme.spacing.xs (look for the "description"
style object and the theme references like theme.spacing.xs and
theme.colors.mutedForeground).

In `@packages/react-web/src/theme/default.ts`:
- Around line 63-69: The defaultTheme object is mutable at the type level;
update the declaration of defaultTheme to be readonly by adding "as const" while
still validating against Theme (i.e., declare it as const defaultTheme = { ... }
as const satisfies Theme); locate the defaultTheme symbol in this file and
change its declaration so nested tokens become readonly and TypeScript won't
allow assignments like defaultTheme.colors.primary = "...".

In `@packages/react/src/use-data-table.ts`:
- Around line 85-97: The effect/callback that calls fetchHook (the block using
fetchHook({ type: 'collection', context: context ?? {}, ... })) captures
`context` but does not list it in its dependency array; add `context` to the
dependency array (alongside hooks?.fetch, scope, page, limit, sortField,
sortOrder, filtersKey, component) so the fetch logic (and the fetchIdRef/current
check and setLoading(false) in the finally) will re-run when `context` changes.
- Around line 9-17: The initial state sets rows to value but leaves total as 0,
causing a first-render mismatch; update the useState for total to initialize
with value.length so total mirrors rows on mount (keep the existing useEffect
that overrides when hooks?.fetch?.[scope] is present); locate the declarations
of rows/setRows and total/setTotal in use-data-table.ts and change the total
initialization accordingly to use value.length.
- Around line 84-95: The current call uses Promise.resolve(fetchHook({...}))
which calls fetchHook synchronously and can throw before the promise chain runs,
leaving loading stuck; change it to defer execution by using
Promise.resolve().then(() => fetchHook({ type: 'collection', context: context ??
{}, params: { page, limit, sort: sortField, order: sortOrder, filters },
hydrate, component, })).finally(() => { if (fetchIdRef.current !== id) return;
setLoading(false); }); so synchronous throws are caught by the promise chain and
.finally always runs.

In `@packages/svelte/src/use-data-form.ts`:
- Around line 84-85: When you apply committed overrides (the spot where
fieldOverrides.set(currentOverrides) is called), snapshot a copy of
currentOverrides into a persistent variable (e.g., committedFieldOverrides) so
those override flags become the baseline; then update reset() so that when no
explicit values are passed it restores fieldOverrides from that committed
snapshot instead of clearing to {}. Ensure the snapshot is a defensive copy (not
the original object) and reference the symbols currentOverrides,
fieldOverrides.set(...), committedFieldOverrides (new variable) and reset() when
making the change.

---

Outside diff comments:
In `@packages/react/src/use-data-form.ts`:
- Around line 278-304: The memoized actions' execute closures capture form
helpers and state (errors, dirty, valid, reset, validate, setErrors, valueRef)
but the useMemo dependency array only lists [schema.actions, scope, handlers,
state, component, permissions], causing stale closures; update the dependency
array for the useMemo that produces actions to include errors, dirty, valid,
reset, validate, setErrors, and valueRef.current (or valueRef) so execute uses
fresh references, or alternatively avoid closing over mutable values by reading
from refs/handlers at call-time (e.g., use valueRef.current and handlers[name]
inside execute) and ensure all referenced symbols (actions, execute, handlers,
state, component, errors, dirty, valid, reset, validate, setErrors, valueRef)
are reflected in dependencies.

In `@packages/svelte/src/use-data-form.ts`:
- Around line 71-72: The dirty baseline is updated after publishing the hydrated
state, so the dirty subscriber recomputes against the old initialState; swap the
operations so the baseline is updated first: call Object.assign(initialState,
currentState) before invoking state.set(currentState) (or otherwise ensure
initialState is updated prior to state.set) so the dirty computation uses the
new baseline when the state is published.
- Around line 38-91: The run function can leave loading=true when fetchHook or
bootstrapHook reject and currently its unhandled rejection bubbles from the
run() call; wrap the orchestration inside run in a try/finally so
loading.set(false) always runs (and still set fieldOverrides/state inside the
try), and add an explicit catch inside run to rethrow or return a rejected
Promise with the original error (so callers can handle it). Specifically modify
run (and its invocation run()) to: surround the existing fetchHook/ events/
bootstrapHook logic with try { ... } finally { loading.set(false) }, and in the
try add a catch block that logs or wraps the error then rethrows; ensure run
returns the Promise so the external run().catch(...) can handle failures. Ensure
you still update fieldOverrides/state inside the try before finally runs.

---

Nitpick comments:
In `@packages/core/src/fields/base.ts`:
- Around line 101-110: In cloneWithFunctions, the check "if (typeof value ===
'function')" is unreachable because the prior "if (value === null || typeof
value !== 'object') return value" already returns for functions; remove the
redundant function-type branch from cloneWithFunctions and keep the early return
for non-objects/null, the Array.isArray(value) branch, and the recursive
object-cloning loop so functions remain returned as-is while objects/arrays are
cloned.

In `@packages/core/src/fields/list.ts`:
- Around line 30-43: The methods events(), hooks(), and handlers() currently
accept the loose Record<string, unknown>; replace those parameter types with the
specific contract types (e.g., SchemaEvents, SchemaHooks, SchemaHandlers) used
by your schema/field system so consumers get stronger typing and autocomplete;
update the function signatures for events(), hooks(), and handlers() to use
those types, adjust imports or generics where needed, and ensure assignments to
this._config.attrs remain compatible (adding casts only if absolutely
necessary).

In `@packages/core/src/types.ts`:
- Around line 210-217: FetchHookContext currently uses PaginateParams for the
'record' variant, which is semantically wrong and forces callers to pass
placeholder pagination values; change the type by replacing PaginateParams in
the 'record' branch with a new minimal params type (e.g., RecordParams or
RecordFetchParams) that contains only the record identifier(s) needed by your
API (or make params optional/absent if the identifier lives in context), update
the FetchHookContext declaration to reference this new type for the branch where
type: 'record', and adjust any usages of FetchHookContext.fetch/hydrate,
component, or callers to pass the new smaller params shape (or remove dummy
{page, limit} values) so callers no longer need to supply pagination for
single-record fetches.

In `@packages/demo/src/settings/hooks.ts`:
- Around line 13-26: The handlers for Scope.view and Scope.edit duplicate the
same fetch/hydrate logic; extract a shared async helper (e.g.,
fetchAndHydrateRecord(context: FetchHookContext)) that performs the early return
when context.type !== FetchType.record or missing context.context.id, calls
service.read(context.context.id as string), then calls context.hydrate(data),
and invoke that helper from both the [Scope.view] and [Scope.edit] handlers so
behavior and types for FetchHookContext, FetchType, service.read and
context.hydrate are preserved.

In `@packages/react-web/src/components/defaults/Pagination.tsx`:
- Line 35: Replace the Portuguese inline comments in the Pagination component
with English equivalents to improve consistency and maintainability: locate the
comments in the Pagination.tsx component (e.g., the comment reading "Esquerda:
Itens por página" and the other comment noted by the reviewer) and update them
to concise English descriptions (for example "Left: Items per page" or similar)
preserving meaning and context near the related JSX elements or props.

In `@packages/react-web/src/components/Table.tsx`:
- Around line 405-411: The code mixes string literals and the Position enum for
action positions; replace the string 'top' used in the filter at the
showTopActions render with the enum value Position.top to match usage like
Position.header and preserve type safety — update the filter on table.actions
(the .filter((a) => a.config.positions.includes('top')) expression) to use
Position.top and ensure Position is imported/available where
ResolvedActionButton is rendered.

In `@packages/react-web/src/renderers/list/useListComponent.ts`:
- Around line 13-18: The inferScope function currently uses strict equality
between the incoming path and DEFAULT_SCOPES' route.path which fails for
concrete parameterized segments (e.g., '123' vs ':id'); update this by either
(A) changing inferScope to match parameterized routes (e.g., normalize/match
route.path patterns like ':id' or '/:id/edit' against actual path segments) or
(B) explicitly document/guard its intended use with route constants only—add a
short comment above inferScope and, if used in handleNavigation, ensure callers
pass route constants (not raw URLs) or perform normalization before calling
inferScope; reference function name inferScope and DEFAULT_SCOPES and the
consumer handleNavigation when making the change.

In `@packages/react-web/src/renderers/ListField.tsx`:
- Around line 68-71: The refs rawValueRef and dialogStateRef in ListField are
created and updated but never used; either remove the unused declarations and
their assignments (delete const rawValueRef = useRef(rawValue);
rawValueRef.current = rawValue; const dialogStateRef = useRef(dialogState);
dialogStateRef.current = dialogState) or, if they were meant to be consumed by
children or hooks, explicitly pass those refs into the child components or hooks
that need them (e.g., as props or via context) so usage is visible; update any
related code in the ListField component to reflect the removal or the new
prop/context usage.
- Around line 94-117: The modal markup rendered by createPortal lacks an
explicit close control; add a visible, focusable close button inside the inner
modal div (the element with style={styles.modal}) that calls closeDialog on
click, includes an accessible label (e.g., aria-label="Close" or
aria-label="Close dialog") and keyboard support (focusable via Tab and triggers
close on Enter/Space), and ensure it is reachable by screen readers near the
DataForm; optionally hook an onKeyDown for Escape to call closeDialog if not
already handled elsewhere.
- Around line 32-47: The reactive array wrapper overrides Array.push and
Array.splice but misimplements splice default and can surprise callers because
mutations aren’t in-place; change splice’s default deleteCount to (next.length -
start) so removing until end behaves like native (use deleteCount ??
(next.length - start)), and update push/splice behavior to more closely mimic
native semantics by either (a) applying mutations to the proxy/returned array
synchronously before calling onChangeRef.current or (b) replace this pattern
with a Proxy that intercepts mutations and forwards them to onChangeRef.current
while reflecting changes immediately; locate and modify the reactiveValue
creation and its push/splice implementations (and keep onChangeRef.current(next)
calls) to ensure return values and in-place mutation semantics match native
Array expectations.

In `@packages/react-web/src/renderers/SelectField.tsx`:
- Line 24: The code calls getComponent('SelectInput') on every render which can
be expensive; change CustomSelect to be obtained once by memoizing the lookup —
e.g., wrap getComponent('SelectInput') in a React useMemo or ensure getComponent
caches internally — so replace the repeated call that assigns const CustomSelect
= getComponent('SelectInput') with a memoized result tied to no or appropriate
dependencies (referencing CustomSelect and getComponent('SelectInput') to locate
the site).

In `@packages/react-web/src/types.ts`:
- Line 79: Update the JSDoc comment for the DataTableEmptyStateInput type to
English to match the codebase language; locate the comment currently in
Portuguese above DataTableEmptyStateInput and replace it with an English
equivalent describing the props passed by the consumer and that `action` accepts
a name or an ActionConfig from the schema.

In `@packages/react/src/use-data-form.ts`:
- Around line 39-40: Replace the Portuguese inline comments in
packages/react/src/use-data-form.ts with clear English equivalents to keep code
consistent; specifically update the comments near the fetchHook block ("fetch →
hidrata os dados"), the comment around the hydrate/events application ("aplica
hydrate e dispara events"), and the bootstrap/state-orchestration comment
("bootstrap → orquestra estado do schema") to concise English phrases (e.g.,
"fetch → hydrate data", "apply hydrate and emit events", "bootstrap →
orchestrate schema state") while leaving surrounding logic (fetchHook, hydrate,
event dispatching, bootstrap/orchestration code paths) unchanged.

In `@packages/svelte/src/use-data-table.ts`:
- Around line 94-116: The Promise.resolve wrapper around fetchHook currently
swallows rejections; add error propagation and state handling by attaching a
.catch handler to the Promise.resolve(fetchHook(...)) call that (1) checks the
stale-request guard (fetchId !== id) before acting, (2) sets loading.set(false)
as needed, and (3) surfaces the error to the consumer (e.g., call an onError
callback or set an error store/state) so callers of useDataTable can distinguish
failures from empty results; update references to hydrate, fetchHook, fetchId,
id, and loading (and optionally rows/total) when implementing this error path.

In `@packages/vue/src/use-data-form.ts`:
- Line 35: Replace the three Portuguese inline comments in use-data-form.ts with
English equivalents for consistency: change the comment at the start of the data
fetch block ("hidrata os dados") to "fetch → hydrate the data", the comment near
the hydrate/events application ("aplica hydrate e dispara events") to "apply
hydrate and emit events", and the comment around schema orchestration
("orquestra estado do schema") to "orchestrate schema state"; update the
comments adjacent to the functions/blocks that perform these tasks (e.g., the
fetch/hydrate block, the hydrate+events handler, and the schema orchestration
logic) so the intent is clear in English.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4d84c580-6eaa-4922-9a0c-a8eaf51c600b

📥 Commits

Reviewing files that changed from the base of the PR and between 52c6168 and 8773a6b.

📒 Files selected for processing (48)
  • packages/core/src/fields/base.ts
  • packages/core/src/fields/list.ts
  • packages/core/src/index.ts
  • packages/core/src/locales/pt-BR.ts
  • packages/core/src/mock.ts
  • packages/core/src/schema.ts
  • packages/core/src/types.ts
  • packages/demo/src/settings/hooks.ts
  • packages/react-web/src/components/DataPage.tsx
  • packages/react-web/src/components/Dialog.tsx
  • packages/react-web/src/components/Form.tsx
  • packages/react-web/src/components/PageActionsContext.tsx
  • packages/react-web/src/components/Table.tsx
  • packages/react-web/src/components/defaults/ColumnSelector.tsx
  • packages/react-web/src/components/defaults/DebugPanel.tsx
  • packages/react-web/src/components/defaults/EmptyState.tsx
  • packages/react-web/src/components/defaults/Pagination.tsx
  • packages/react-web/src/components/registry.ts
  • packages/react-web/src/contracts/component.ts
  • packages/react-web/src/index.ts
  • packages/react-web/src/renderers/DateField.tsx
  • packages/react-web/src/renderers/ListField.tsx
  • packages/react-web/src/renderers/MultiSelectField.tsx
  • packages/react-web/src/renderers/NumberField.tsx
  • packages/react-web/src/renderers/SelectField.tsx
  • packages/react-web/src/renderers/TextField.tsx
  • packages/react-web/src/renderers/TextareaField.tsx
  • packages/react-web/src/renderers/TimeField.tsx
  • packages/react-web/src/renderers/ToggleField.tsx
  • packages/react-web/src/renderers/list/useListComponent.ts
  • packages/react-web/src/renderers/list/useListDialog.ts
  • packages/react-web/src/support/Icon.tsx
  • packages/react-web/src/support/i18n.ts
  • packages/react-web/src/theme/default.ts
  • packages/react-web/src/types.ts
  • packages/react/src/types.ts
  • packages/react/src/use-data-form.ts
  • packages/react/src/use-data-table.ts
  • packages/svelte/src/types.ts
  • packages/svelte/src/use-data-form.ts
  • packages/svelte/src/use-data-table.ts
  • packages/vue/src/types.ts
  • packages/vue/src/use-data-form.ts
  • packages/vue/src/use-data-table.ts
  • playground/react-native/tests/src/domain/person/hooks.test.ts
  • playground/react-web/tests/src/domain/person/hooks.test.ts
  • playground/sveltekit/tests/src/domain/person/hooks.test.ts
  • playground/vue-quasar/tests/src/domain/person/hooks.test.ts

Comment on lines 49 to +67
if (loading) {
return (
<div style={styles.loading}>Loading...</div>
);
)
}

if (!permitted) {
if (forbidden) return <>{forbidden}</>;
if (forbidden) return <>{forbidden}</>
return (
<div style={styles.forbidden}>
<Icon name="shield-off" size={32} color={theme.colors.mutedForeground} />
<div style={styles.forbiddenText}>{t("common.forbidden")}</div>
<Icon
name="shield-off"
size={32}
color={theme.colors.mutedForeground}
/>
<div style={styles.forbiddenText}>{t('common.forbidden')}</div>
</div>
);
)
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don’t force full-screen loading/forbidden UIs in flat or bare mode.

These early returns bypass the new flat-layout path entirely. layout="flat"/bare pages still get minHeight: '100vh' and page chrome during loading or forbidden states, which breaks the new host-controlled layout contract exactly when the state flips.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react-web/src/components/DataPage.tsx` around lines 49 - 67, The
DataPage component currently early-returns full-screen loading/forbidden UIs
(using loading, permitted, forbidden and styles.loading/styles.forbidden), which
breaks host-controlled layouts for pages rendered with layout="flat" or bare
mode; change the render logic to avoid early returns so flat/bare pages are not
replaced with a 100vh wrapper: detect flat/bare (props.layout === 'flat' or
props.bare) and for those cases render inline/smaller loading or forbidden
placeholders (reuse Icon and styles.forbiddenText but remove minHeight: '100vh'
and the full-screen container) or render the existing children plus a
lightweight overlay; otherwise keep the current full-screen behavior for
non-flat pages. Ensure you update the conditional branches around
loading/permitted/forbidden in DataPage to return the children/layout path for
flat/bare while showing a non-fullscreen indicator.

Comment on lines +74 to +84
const resolvedHeaderEnd = headerEnd ?? registeredActions
return (
<PageActionsContext.Provider value={contextValue}>
<div style={styles.flatScroll}>
<div style={{ ...styles.flatContainer, maxWidth }}>
<div style={styles.flatHeader}>
<div>
<div style={styles.flatTitle}>{resolvedTitle}</div>
{subtitle && <div style={styles.flatSubtitle}>{subtitle}</div>}
</div>
{resolvedHeaderEnd && <div style={styles.flatHeaderEnd}>{resolvedHeaderEnd}</div>}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Compose headerEnd with registered page actions instead of replacing them.

headerEnd ?? registeredActions hides the actions registered by nested children whenever the page also passes headerEnd. With the new form-header integration, that can remove the only save/cancel controls on customized flat pages.

♻️ Suggested composition
-    const resolvedHeaderEnd = headerEnd ?? registeredActions
+    const resolvedHeaderEnd =
+      headerEnd != null || registeredActions != null
+        ? (
+            <>
+              {headerEnd}
+              {registeredActions}
+            </>
+          )
+        : null
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const resolvedHeaderEnd = headerEnd ?? registeredActions
return (
<PageActionsContext.Provider value={contextValue}>
<div style={styles.flatScroll}>
<div style={{ ...styles.flatContainer, maxWidth }}>
<div style={styles.flatHeader}>
<div>
<div style={styles.flatTitle}>{resolvedTitle}</div>
{subtitle && <div style={styles.flatSubtitle}>{subtitle}</div>}
</div>
{resolvedHeaderEnd && <div style={styles.flatHeaderEnd}>{resolvedHeaderEnd}</div>}
const resolvedHeaderEnd =
headerEnd != null || registeredActions != null
? (
<>
{headerEnd}
{registeredActions}
</>
)
: null
return (
<PageActionsContext.Provider value={contextValue}>
<div style={styles.flatScroll}>
<div style={{ ...styles.flatContainer, maxWidth }}>
<div style={styles.flatHeader}>
<div>
<div style={styles.flatTitle}>{resolvedTitle}</div>
{subtitle && <div style={styles.flatSubtitle}>{subtitle}</div>}
</div>
{resolvedHeaderEnd && <div style={styles.flatHeaderEnd}>{resolvedHeaderEnd}</div>}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react-web/src/components/DataPage.tsx` around lines 74 - 84, The
current DataPage uses resolvedHeaderEnd = headerEnd ?? registeredActions which
drops registeredActions when headerEnd is provided; instead compose both so
nested-registered actions are not hidden: in the DataPage render (look for
headerEnd, registeredActions, resolvedHeaderEnd) replace the nullish-coalescing
behavior with a composed fragment/element that renders registeredActions and
headerEnd together (preserving desired order and spacing) and use that composed
value where resolvedHeaderEnd is used so both sets of controls show.

Comment on lines +9 to +14
function getVisiblePages(current: number, total: number): number[] {
if (total <= 5) return Array.from({ length: total }, (_, i) => i + 1);
const start = Math.max(1, current - 2);
const end = Math.min(total, start + 4);
return Array.from({ length: end - start + 1 }, (_, i) => start + i);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Sliding window may show fewer than 5 pages near boundaries.

When current is near the end of pagination (e.g., current=9, total=10), the calculation yields start=7, end=10, producing only 4 page buttons instead of 5. The start is not re-adjusted after end is clamped.

🔧 Proposed fix to maintain consistent window size
 function getVisiblePages(current: number, total: number): number[] {
   if (total <= 5) return Array.from({ length: total }, (_, i) => i + 1);
-  const start = Math.max(1, current - 2);
-  const end = Math.min(total, start + 4);
+  let start = Math.max(1, current - 2);
+  let end = Math.min(total, start + 4);
+  // Re-adjust start if end was clamped
+  start = Math.max(1, end - 4);
   return Array.from({ length: end - start + 1 }, (_, i) => start + i);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function getVisiblePages(current: number, total: number): number[] {
if (total <= 5) return Array.from({ length: total }, (_, i) => i + 1);
const start = Math.max(1, current - 2);
const end = Math.min(total, start + 4);
return Array.from({ length: end - start + 1 }, (_, i) => start + i);
}
function getVisiblePages(current: number, total: number): number[] {
if (total <= 5) return Array.from({ length: total }, (_, i) => i + 1);
let start = Math.max(1, current - 2);
let end = Math.min(total, start + 4);
// Re-adjust start if end was clamped
start = Math.max(1, end - 4);
return Array.from({ length: end - start + 1 }, (_, i) => start + i);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react-web/src/components/defaults/Pagination.tsx` around lines 9 -
14, The getVisiblePages function can produce fewer than 5 pages near the end
because start isn't re-adjusted after end is clamped; update getVisiblePages to
ensure a consistent 5-item sliding window by computing end = Math.min(total,
start + 4) and then, if (end - start + 1) < 5, shift start = Math.max(1, end -
4) (or equivalently compute start = Math.max(1, Math.min(current - 2, total -
4))) so the returned Array.from(...) always yields up to 5 page numbers when
total >= 5.

Comment on lines +36 to +50
useLayoutEffect(() => {
if (!pageActions) return
const headerActions = form.actions.filter((a) => a.config.positions.includes(Position.header))
if (headerActions.length === 0) return
pageActions.register(
<div style={{ display: 'flex', gap: 8 }}>
{headerActions.map((a) => <ResolvedActionButton
key={a.name}
action={a}
domain={props.schema.domain}
/>)}
</div>
)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [pageActions, props.schema.domain])
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Make the page-actions registration reactive and clear it on teardown.

The eslint-disable is masking a real stale-state bug here. This effect only reruns for pageActions/props.schema.domain, so header actions won’t update when form.actions changes, and a previously registered node is never cleared when the form unmounts or stops exposing header actions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react-web/src/components/Form.tsx` around lines 36 - 50, The effect
in useLayoutEffect is stale because it only depends on pageActions and
props.schema.domain and never cleans up the registered node; update the
dependency array to include form.actions (or the derived headerActions) so the
effect re-runs when form.actions changes, remove the eslint-disable comment, and
add a cleanup that removes the previously registered element—capture the return
value from pageActions.register (or the DOM node you passed) and call the
corresponding unregister/remove API in the cleanup function so the header
buttons are deregistered when headerActions becomes empty or on unmount; target
symbols: useLayoutEffect, pageActions.register, form.actions, headerActions,
ResolvedActionButton.

const ResolvedColumnSelector = components?.ColumnSelector ?? getComponent('ColumnSelector') ?? DefaultColumnSelector
const ResolvedSearchBar = components?.SearchBar ?? getComponent('SearchBar') as React.ComponentType<SearchBarProps> | undefined
const EmptyComponent = components?.EmptyState ?? getComponent('EmptyState') ?? DefaultEmptyState
const [search, setSearch] = useState('')
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check if search is meant to be wired to table filtering
rg -n "setFilter.*search|search.*setFilter" --type ts

Repository: devitools/ybyra

Length of output: 41


🏁 Script executed:

cd packages/react-web/src/components && wc -l Table.tsx

Repository: devitools/ybyra

Length of output: 72


🏁 Script executed:

cd packages/react-web/src/components && sed -n '300,320p' Table.tsx

Repository: devitools/ybyra

Length of output: 1308


🏁 Script executed:

cd packages/react-web/src/components && sed -n '420,440p' Table.tsx

Repository: devitools/ybyra

Length of output: 676


🏁 Script executed:

cd packages/react-web/src/components && rg -n "table\.setFilter|setFilter" Table.tsx

Repository: devitools/ybyra

Length of output: 41


🏁 Script executed:

cd packages/react-web/src/components && rg -n "search" Table.tsx -A 2 -B 2

Repository: devitools/ybyra

Length of output: 1171


🏁 Script executed:

find packages -name "*.tsx" -o -name "*.ts" | xargs rg -l "SearchBar" | head -5

Repository: devitools/ybyra

Length of output: 212


🏁 Script executed:

rg -n "SearchBarProps|interface SearchBar" packages/react-web/src --type ts -A 10

Repository: devitools/ybyra

Length of output: 4969


🏁 Script executed:

rg -n "setFilter" packages/react-web/src --type ts -B 2 -A 2

Repository: devitools/ybyra

Length of output: 41


🏁 Script executed:

cd packages/react-web/src && find . -name "*SearchBar*" -o -name "*searchBar*"

Repository: devitools/ybyra

Length of output: 41


🏁 Script executed:

rg -n "useDataTable" packages/react-web/src --type ts -l

Repository: devitools/ybyra

Length of output: 102


🏁 Script executed:

cat -n packages/react-web/src/hooks/useDataTable.ts | head -100

Repository: devitools/ybyra

Length of output: 135


🏁 Script executed:

rg -n "setSearch\|search.*=" packages/react-web/src/components/Table.tsx -B 3 -A 3

Repository: devitools/ybyra

Length of output: 41


🏁 Script executed:

find packages -name "*.ts" -o -name "*.tsx" | xargs rg -l "useDataTable" | head -5

Repository: devitools/ybyra

Length of output: 240


🏁 Script executed:

rg -n "function useDataTable|const useDataTable|export.*useDataTable" packages --type ts -B 2 -A 20

Repository: devitools/ybyra

Length of output: 9723


🏁 Script executed:

cd packages/react-web/src/components && sed -n '1,50p' Table.tsx | grep -E "import|from"

Repository: devitools/ybyra

Length of output: 1179


🏁 Script executed:

cat -n packages/react-web/src/components/Table.tsx | sed -n '1,30p'

Repository: devitools/ybyra

Length of output: 1569


🏁 Script executed:

find packages/react-web -name "use-data-table.ts" -o -name "useDataTable.ts"

Repository: devitools/ybyra

Length of output: 41


🏁 Script executed:

rg -n "setFilter" packages/react --type ts -B 5 -A 5

Repository: devitools/ybyra

Length of output: 7997


🏁 Script executed:

cat -n packages/react-web/src/components/Table.tsx | sed -n '310,450p'

Repository: devitools/ybyra

Length of output: 5595


🏁 Script executed:

rg -n "useEffect.*search|search.*useEffect" packages/react-web/src/components/Table.tsx -B 3 -A 5

Repository: devitools/ybyra

Length of output: 41


🏁 Script executed:

find packages -path "*/node_modules" -prune -o -name "*.tsx" -type f -exec grep -l "SearchBar" {} \; | grep -v node_modules

Repository: devitools/ybyra

Length of output: 102


🏁 Script executed:

cat -n packages/react-web/src/components/Table.tsx | sed -n '310,600p' | grep -E "useEffect|search|setFilter" -B 2 -A 2

Repository: devitools/ybyra

Length of output: 915


🏁 Script executed:

rg -n "table\.(setFilter|filters)" packages/react-web/src/components/Table.tsx -B 3 -A 3

Repository: devitools/ybyra

Length of output: 596


search state is unused for filtering.

The search state is initialized and passed to ResolvedSearchBar, but it's never connected to table.setFilter or any other filtering mechanism. The search functionality appears incomplete.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react-web/src/components/Table.tsx` at line 312, The search state
(const [search, setSearch]) is never applied to the table, so typing in
ResolvedSearchBar doesn't filter results; hook the state up to the table by
calling the table filtering API whenever search changes (e.g., in a useEffect
call table.setGlobalFilter(search) or table.setFilter('<yourColumnId>', search)
depending on whether you use a global filter or a specific column filter).
Ensure the effect runs on mount and when search updates and that clearing search
clears the table filter; keep setSearch passed into ResolvedSearchBar as-is.

Comment on lines 63 to +69
fontWeight: {
regular: "400" as const,
medium: "500" as const,
semibold: "600" as const,
bold: "700" as const,
regular: "400",
medium: "500",
semibold: "600",
bold: "700",
},
} as const;
} satisfies Theme;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In TypeScript, when an object literal changes from as consttosatisfies SomeInterface, do nested properties remain readonly or become mutable/widened? Is const theme = { ... } as const satisfies Theme valid, and what is the recommended pattern to preserve readonly theme tokens while still checking the object against an exported interface?

💡 Result:

  • Replacing as const with satisfies Theme does not make the object (or nested properties) readonly. satisfies is only a compatibility check and “doesn’t chang[e] the resulting type of that expression” [2]. So if you write:

    const theme = { colors: { primary: "#09f" } } satisfies Theme;

    the object’s type is still inferred like a normal object literal (properties are mutable, and string/number literals typically widen to string/number unless some other const-context inference applies).

  • const theme = { ... } as const satisfies Theme is valid TypeScript and is a common pattern: as const prevents widening and makes object-literal properties readonly [1], while satisfies Theme checks it against Theme without re-typing it as Theme [2].

  • The main gotcha: if Theme is written with mutable shapes (e.g. string[], non-readonly properties), then as const satisfies Theme may fail, because as const produces readonly properties/tuples/arrays [1]. In that case, either:

    1. Make the exported contract readonly-friendly (recommended if you truly want immutable tokens):
      export interface Theme {
        readonly colors: {
          readonly primary: string;
        };
        // use readonly arrays/tuples where relevant
        // readonly space: readonly number[];
      }
      
      export const theme = {
        colors: { primary: "#09f" },
      } as const satisfies Theme;
    2. Or (often best for “design tokens”): export the value and derive the type from it, so the source of truth stays the object literal:
      export const theme = {
        colors: { primary: "#09f" },
      } as const;
      
      export type Theme = typeof theme;
      If you still need an interface for external consumption, keep it as a separate “shape” and validate with satisfies, but expect to model readonly-ness there too.

Sources: TypeScript 3.4 const assertions (as const) prevent widening and make object literal properties readonly [1]; TypeScript 4.9 satisfies checks compatibility “without changing the resulting type” [2].

[1] https://devblogs.microsoft.com/typescript/announcing-typescript-3-4/
[2] https://devblogs.microsoft.com/typescript/announcing-typescript-4-9/


🏁 Script executed:

cat packages/react-web/src/theme/default.ts

Repository: devitools/ybyra

Length of output: 2062


Add as const to preserve defaultTheme immutability.

Without as const, the object's properties are mutable at the type level. Callers can reassign nested tokens like defaultTheme.colors.primary = "..." and the change affects every consumer. Use const defaultTheme = { ... } as const satisfies Theme; to prevent widening and make properties readonly while still checking the shape against the Theme interface.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react-web/src/theme/default.ts` around lines 63 - 69, The
defaultTheme object is mutable at the type level; update the declaration of
defaultTheme to be readonly by adding "as const" while still validating against
Theme (i.e., declare it as const defaultTheme = { ... } as const satisfies
Theme); locate the defaultTheme symbol in this file and change its declaration
so nested tokens become readonly and TypeScript won't allow assignments like
defaultTheme.colors.primary = "...".

Comment on lines +9 to +17
const [rows, setRows] = useState<Record<string, unknown>[]>(value);
const [loading, setLoading] = useState(false);
const [total, setTotal] = useState(0);

useEffect(() => {
if (hooks?.fetch?.[scope]) return; // fetch hook gerencia os dados
setRows(value);
setTotal(value.length);
}, [value, hooks?.fetch, scope]);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

fd -t f "use-data-table.ts" packages/react/src

Repository: devitools/ybyra

Length of output: 95


🏁 Script executed:

cat -n packages/react/src/use-data-table.ts | head -50

Repository: devitools/ybyra

Length of output: 2435


🏁 Script executed:

cd packages/react/src && ls -la use-data-table.ts

Repository: devitools/ybyra

Length of output: 125


🏁 Script executed:

cat -n packages/react/src/use-data-table.ts

Repository: devitools/ybyra

Length of output: 10554


Initialize total to match rows on first render.

When the component mounts, rows is seeded with value but total remains 0 until the effect runs. This creates a first-render mismatch where rows.length differs from total, breaking pagination calculations like totalPages = Math.ceil(total / limit) (line 104). This is particularly problematic during SSR where the initial render is output as-is. Initialize total with value.length to keep both state variables consistent on mount.

Proposed fix
-  const [total, setTotal] = useState(0);
+  const [total, setTotal] = useState(value.length);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const [rows, setRows] = useState<Record<string, unknown>[]>(value);
const [loading, setLoading] = useState(false);
const [total, setTotal] = useState(0);
useEffect(() => {
if (hooks?.fetch?.[scope]) return; // fetch hook gerencia os dados
setRows(value);
setTotal(value.length);
}, [value, hooks?.fetch, scope]);
const [rows, setRows] = useState<Record<string, unknown>[]>(value);
const [loading, setLoading] = useState(false);
const [total, setTotal] = useState(value.length);
useEffect(() => {
if (hooks?.fetch?.[scope]) return; // fetch hook gerencia os dados
setRows(value);
setTotal(value.length);
}, [value, hooks?.fetch, scope]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react/src/use-data-table.ts` around lines 9 - 17, The initial state
sets rows to value but leaves total as 0, causing a first-render mismatch;
update the useState for total to initialize with value.length so total mirrors
rows on mount (keep the existing useEffect that overrides when
hooks?.fetch?.[scope] is present); locate the declarations of rows/setRows and
total/setTotal in use-data-table.ts and change the total initialization
accordingly to use value.length.

Comment on lines +84 to +95
Promise.resolve(
fetchHook({
type: 'collection',
context: context ?? {},
params: { page, limit, sort: sortField, order: sortOrder, filters },
hydrate,
component,
}),
).finally(() => {
if (fetchIdRef.current !== id) return;
setLoading(false);
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n packages/react/src/use-data-table.ts | sed -n '75,105p'

Repository: devitools/ybyra

Length of output: 1194


🏁 Script executed:

# Search for fetchHook definition and its usage
rg -A 5 -B 5 'fetchHook' packages/react/src/use-data-table.ts

Repository: devitools/ybyra

Length of output: 684


🏁 Script executed:

# Check the function signature and parameters
rg 'function.*fetchHook|const.*fetchHook|type.*fetchHook' packages/react/

Repository: devitools/ybyra

Length of output: 1475


🏁 Script executed:

# Look for type definitions of the hooks parameter
rg -B 10 "fetchHook" packages/react/src/use-data-table.ts | head -40

Repository: devitools/ybyra

Length of output: 693


🏁 Script executed:

# Check the interface/type for hooks and fetch
ast-grep --pattern 'interface $_ { 
  $$$
  fetch $$$
}'

Repository: devitools/ybyra

Length of output: 325


🏁 Script executed:

# Look for type definitions
rg 'type.*fetch|interface.*fetch' packages/react/src/ -A 5

Repository: devitools/ybyra

Length of output: 41


🏁 Script executed:

# Check the full function signature
sed -n '1,75p' packages/react/src/use-data-table.ts | tail -30

Repository: devitools/ybyra

Length of output: 998


🏁 Script executed:

# Look for FetchHookFn type definition
rg 'FetchHookFn' packages/core/src/types.ts -A 10

Repository: devitools/ybyra

Length of output: 639


🏁 Script executed:

# Check if there are any other relevant type definitions
rg 'type FetchHookFn|interface FetchHookFn' packages/core/

Repository: devitools/ybyra

Length of output: 191


🏁 Script executed:

# Check all fetchHook implementations in the codebase
rg 'fetch.*:.*\(' packages/ -A 3 | head -50

Repository: devitools/ybyra

Length of output: 41


🏁 Script executed:

# Look for any synchronous fetch implementations
rg -B 5 -A 10 '\[.*Scope\.' packages/ | grep -A 10 'fetch:'

Repository: devitools/ybyra

Length of output: 14873


Wrap fetchHook invocation in promise chain to guard against synchronous throws.

FetchHookFn type allows synchronous implementations ((ctx) => void). If such an implementation throws, Promise.resolve(fetchHook(...)) executes the call synchronously before entering the promise chain, so .finally() never runs and loading remains stuck at true. Use .then() to defer execution:

Proposed fix
-    Promise.resolve(
-      fetchHook({
-        type: 'collection',
-        context: context ?? {},
-        params: { page, limit, sort: sortField, order: sortOrder, filters },
-        hydrate,
-        component,
-      }),
-    ).finally(() => {
-      if (fetchIdRef.current !== id) return;
-      setLoading(false);
-    });
+    Promise.resolve()
+      .then(() => fetchHook({
+        type: 'collection',
+        context: context ?? {},
+        params: { page, limit, sort: sortField, order: sortOrder, filters },
+        hydrate,
+        component,
+      }))
+      .finally(() => {
+        if (fetchIdRef.current !== id) return;
+        setLoading(false);
+      });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Promise.resolve(
fetchHook({
type: 'collection',
context: context ?? {},
params: { page, limit, sort: sortField, order: sortOrder, filters },
hydrate,
component,
}),
).finally(() => {
if (fetchIdRef.current !== id) return;
setLoading(false);
});
Promise.resolve()
.then(() => fetchHook({
type: 'collection',
context: context ?? {},
params: { page, limit, sort: sortField, order: sortOrder, filters },
hydrate,
component,
}))
.finally(() => {
if (fetchIdRef.current !== id) return;
setLoading(false);
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react/src/use-data-table.ts` around lines 84 - 95, The current call
uses Promise.resolve(fetchHook({...})) which calls fetchHook synchronously and
can throw before the promise chain runs, leaving loading stuck; change it to
defer execution by using Promise.resolve().then(() => fetchHook({ type:
'collection', context: context ?? {}, params: { page, limit, sort: sortField,
order: sortOrder, filters }, hydrate, component, })).finally(() => { if
(fetchIdRef.current !== id) return; setLoading(false); }); so synchronous throws
are caught by the promise chain and .finally always runs.

Comment on lines +85 to 97
fetchHook({
type: 'collection',
context: context ?? {},
params: { page, limit, sort: sortField, order: sortOrder, filters },
hydrate,
component,
}),
).finally(() => {
if (fetchIdRef.current !== id) return;
setLoading(false);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [hooks?.fetch, scope, page, limit, sortField, sortOrder, filtersKey, component]);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n packages/react/src/use-data-table.ts | sed -n '70,110p'

Repository: devitools/ybyra

Length of output: 1543


🏁 Script executed:

cat -n packages/react/src/use-data-table.ts | head -70

Repository: devitools/ybyra

Length of output: 3194


Add context to the dependency array to ensure fetches reflect context changes.

context is passed in the fetch payload (line 87) but omitted from the reload callback dependencies (line 97). When context changes, the callback continues using the previously captured value, preventing table reloads for new context.

Proposed fix
-  }, [hooks?.fetch, scope, page, limit, sortField, sortOrder, filtersKey, component]);
+  }, [hooks?.fetch, scope, page, limit, sortField, sortOrder, filtersKey, component, context]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fetchHook({
type: 'collection',
context: context ?? {},
params: { page, limit, sort: sortField, order: sortOrder, filters },
hydrate,
component,
}),
).finally(() => {
if (fetchIdRef.current !== id) return;
setLoading(false);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [hooks?.fetch, scope, page, limit, sortField, sortOrder, filtersKey, component]);
fetchHook({
type: 'collection',
context: context ?? {},
params: { page, limit, sort: sortField, order: sortOrder, filters },
hydrate,
component,
}),
).finally(() => {
if (fetchIdRef.current !== id) return;
setLoading(false);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [hooks?.fetch, scope, page, limit, sortField, sortOrder, filtersKey, component, context]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/react/src/use-data-table.ts` around lines 85 - 97, The
effect/callback that calls fetchHook (the block using fetchHook({ type:
'collection', context: context ?? {}, ... })) captures `context` but does not
list it in its dependency array; add `context` to the dependency array
(alongside hooks?.fetch, scope, page, limit, sortField, sortOrder, filtersKey,
component) so the fetch logic (and the fetchIdRef/current check and
setLoading(false) in the finally) will re-run when `context` changes.

Comment on lines 84 to 85
if (Object.keys(currentOverrides).length > 0) {
fieldOverrides.set(currentOverrides)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Persist the committed overrides as part of the reset baseline.

After hydration/bootstrap, currentOverrides becomes part of the initial form shape, but reset() still clears fieldOverrides to {}. That restores the hydrated values while dropping the hidden/disabled/state overrides computed here. Snapshot these overrides when you commit them and reuse that snapshot in reset() when no explicit values are supplied.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/svelte/src/use-data-form.ts` around lines 84 - 85, When you apply
committed overrides (the spot where fieldOverrides.set(currentOverrides) is
called), snapshot a copy of currentOverrides into a persistent variable (e.g.,
committedFieldOverrides) so those override flags become the baseline; then
update reset() so that when no explicit values are passed it restores
fieldOverrides from that committed snapshot instead of clearing to {}. Ensure
the snapshot is a defensive copy (not the original object) and reference the
symbols currentOverrides, fieldOverrides.set(...), committedFieldOverrides (new
variable) and reset() when making the change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant