Skip to content

Implement order creation API, UI components, and filters - #7

Merged
atelesjr merged 28 commits into
mainfrom
create-order
Mar 27, 2026
Merged

Implement order creation API, UI components, and filters#7
atelesjr merged 28 commits into
mainfrom
create-order

Conversation

@atelesjr

@atelesjr atelesjr commented Mar 25, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features
    • Added order creation modal with form validation and submission.
    • Added advanced filtering for orders by instrument, status, side, and date range.
    • Added pagination controls for order list navigation.
    • Added date picker input component for selecting dates.
    • Added reusable UI components: buttons, text inputs, radio inputs, select inputs, and table component.
    • Integrated Storybook for component documentation and preview.

atelesjr added 21 commits March 16, 2026 17:22
- Added API route for creating orders with validation and error handling.
- Created CreateOrderModal component for user input with form validation using react-hook-form and zod.
- Implemented styles for the CreateOrderModal component.
- Added tests for CreateOrderModal to ensure proper functionality and validation.
- Defined schema for order creation using zod to enforce input constraints.
- Add FilterSelect component for selecting filter options.
- Introduce FilterTextInput component for text-based filters.
- Create FiltersActions component for applying and clearing filters.
- Implement FiltersBar to manage filter fields and actions.
- Add FiltersFields to render various filter inputs based on configuration.
- Create Head component for rendering table headers with sorting functionality.
- Implement Pagination component for navigating through paginated data.
- Add Root and Table components for structuring the orders grid layout.
- Create Toolbar for actions and pagination at the top of the grid.
- Add tests for ActionButton, Body, FilterSelect, FiltersBar, Head, and Toolbar components.
- Define filters configuration and utility functions for managing filter states.
- Implement useFiltersBarController for handling filter state and navigation.
- Create useOrderGridWithPaginationController for managing orders and selection state.
- Removed old test files for FiltersBar and Toolbar.
- Deleted filters configuration and controller logic.
- Introduced new Filter components including FilterSelect, FilterDatePicker, and FiltersBar.
- Implemented FiltersActions for applying and clearing filters.
- Created ActionButton and Toolbar components for better action handling.
- Added Pagination component for navigation.
- Updated styles for new filter components.
- Added tests for new FilterSelect and ActionButton components.
@coderabbitai

coderabbitai Bot commented Mar 25, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR introduces a comprehensive order-creation feature and expands the UI component library with Storybook integration. It adds an API route for order submission, a modal-based form with validation, reusable UI components (table, inputs, buttons, date picker), and filter/pagination controls for the order grid. The changes include component tests, Storybook stories, new design tokens, and component documentation.

Changes

Cohort / File(s) Summary
Storybook Setup
.storybook/main.ts, .storybook/preview.ts
New Storybook configuration with Next.js framework, component story paths, and preview settings including global stylesheet import and control matchers for color/date props.
Order Creation API & Schema
app/api/orders/route.ts, lib/orders/create-order.schema.ts, lib/orders/orders.types.ts
New POST endpoint that validates incoming requests against a Zod schema, generates clientOrderId with timestamp-based format, and forwards to upstream API. Extended Order type with clientOrderId, exchangeOrderId, and executionId fields. Schema enforces trimmed instrument (max 30 chars, uppercase), side enum with refinement, and positive finite numbers for price/quantity.
Create Order Modal Component
components/CreateOrderModal/index.tsx, components/CreateOrderModal/useCreateOrderForm.ts, components/CreateOrderModal/CreateOrderModal.styles.css, components/CreateOrderModal/__tests__/CreateOrderModal.test.tsx
New modal component with form handling via react-hook-form, field-level and root-level error display, validation against createOrderSchema, and submission to /api/orders. Tests verify dialog presence, field validation errors, and successful submission flow with modal closure and callback invocation.
Table Component
components/ui/Table/*
Complete generic table component system with compound structure (Root, Table, Head, Body), support for custom column rendering, sortable headers via next/link, empty state messaging, clickable rows with keyboard support (Enter/Space), and responsive styling. Includes Storybook stories and comprehensive test coverage.
Text Input Component
components/ui/inputs/TextInput/*
Accessible text input with optional label, error messaging via aria-describedby, invalid state styling, and dual change callbacks (onChange and onValueChange). Includes stories, tests, and Tailwind-based styling.
Radio Input Component
components/ui/inputs/RadioInput/*
Radio group component supporting multiple options with per-option disable, label display, error messaging with role="alert", and both controlled and uncontrolled modes. Includes stories, tests, and comprehensive styling.
Select Input Component (Compound)
components/ui/inputs/SelectInput/*
Compound select component with context-based state sharing across subcomponents (Root, Label, Trigger, Menu, Error). Manages open/close state, keyboard interactions (Escape), outside-click detection, and option selection. Includes controller hook, context, and full test coverage.
Date Picker Input Component (Compound)
components/ui/inputs/DatePickerInput/*
Date picker with ISO date format (YYYY-MM-DD), calendar grid UI, month navigation, today/clear buttons, and popover positioning. Exports utilities for date parsing/formatting, calendar cell generation, and month labels. Includes context, controller hook, subcomponents (Trigger, Popover, Label, Error), stories, and tests.
Button Components
components/ui/buttons/Button/*, components/ui/buttons/ButtonIcon/*
Generic Button with variants (primary/secondary), sizes (sm/md/lg), and widths (auto/full). ButtonIcon supports icon placement (left/right), optional labels, and size modifiers. Both include Storybook stories, tests, and Tailwind-based styling.
Order Grid Enhancements
components/OrderGrid/index.tsx, components/OrderGrid/useOrderGridWithPaginationController.ts, components/OrderGrid/OrderGridWithPaginationClient.tsx, components/OrderGrid/__tests__/OrderGridWithPaginationClient.test.tsx
Updated grid to use unified Table component with sortable columns via mapColumnsToTable(). Added create-order modal integration via controller state (isCreateOrderOpen, openCreateOrderModal, handleCreateOrderOpenChange). Modal triggers router.refresh() on successful order creation. Imported subcomponents from new ./components/... paths.
Order Grid Filter System
components/OrderGrid/components/Filter/*
New filter UI with text fields, select dropdowns, and date picker. Includes: FiltersBar (form wrapper), FiltersFields (field layout), FiltersActions (apply/clear buttons), FilterSelect (menu select), FilterDatePicker (calendar popover). Controller hook useFiltersBarController manages form state, URL query parameter updates, and pagination reset to page 1. Config module defines filter field metadata and type-safe form state. Tests verify filter application and clearing with correct URL navigation.
Order Grid Toolbar & Pagination
components/OrderGrid/components/Toolbar.tsx, components/OrderGrid/components/Pagination.tsx, components/OrderGrid/components/ActionButton.tsx, components/OrderGrid/components/__tests__/...
New toolbar with OrdersGridActionButton for create/filter actions, displaying filter active state via aria-pressed. Pagination component renders previous/next links and current page indicator. Tests verify button presence, filter active state, and pagination display.
Order Grid Layout Components
components/OrderGrid/components/Root.tsx
Simple wrapper component rendering children in a <section class="orders-grid">.
Modal Stylesheet Update
components/Modal/Modal.styles.css
Updated .modal__title from text-grid-sm to text-size-16 typography token.
Order Grid Stylesheet Updates
components/OrderGrid/OrderGrid.styles.css
Added sr-only class to .ui-table__caption within .orders-grid. Removed all .orders-grid__filters styling and responsive media query overrides (styles now in Filter.styles.css).
Filter Component Stylesheet
components/OrderGrid/components/Filter/Filter.styles.css
Comprehensive filter UI styling with grid layout, field containers, select/date picker dropdowns, z-index layering, calendar grid, and responsive adjustments for small screens.
Design Tokens & Fonts
styles/theme.css, styles/globals.css
Added CSS custom properties: --font-secondary, --color-gray-darker, --color-green-light, --text-size-18, --text-size-16. Added "Wix Madefor Text Regular" font import to global styles.
Database & Configuration
server/db.json, .gitignore, package.json, .vscode/settings.json
Updated mock data: order IDs changed to timestamp format, added clientOrderId/exchangeOrderId/executionId fields, extended dataset to March 2026, added schema reference. Updated .gitignore to exclude Storybook build and debug log. Added npm scripts for Storybook (dev/build). Added dependencies: react-hook-form, zod, zustand. Added uuidv" to spell-check words.
Shared Utilities
lib/shared/uuid.ts
New uuidv4() function generating v4 UUIDs via crypto.randomUUID() or fallback implementation using getRandomValues().
Documentation
docs/patterns/components.md
New component standards guide covering package manager (pnpm only), folder rules (app/ for routes), client/server component usage, accessibility requirements (semantic HTML, focus, error alerts, icon labels), Tailwind constraints (no inline utilities in JSX, use *.styles.css with @reference), component API best practices, compound components, testing/Storybook requirements, and Zustand adoption guidance.

Sequence Diagrams

sequenceDiagram
    participant User
    participant Modal as CreateOrderModal
    participant Form as useCreateOrderForm
    participant API as /api/orders
    participant Upstream as Upstream API
    participant Grid as OrderGrid
    participant Router as router.refresh()

    User->>Modal: Click "Criar ordem"
    User->>Modal: Fill form (instrument, side, price, quantity)
    User->>Modal: Click "Criar"
    Modal->>Form: onSubmit()
    Form->>Form: Validate with createOrderSchema
    alt Validation fails
        Form->>Modal: Set field errors & root error
        Modal->>User: Display error messages
    else Validation passes
        Form->>API: POST validated data
        API->>API: Generate clientOrderId (CL-timestamp-uuid)
        API->>API: Construct order payload
        API->>Upstream: POST to upstream API
        alt Upstream error
            Upstream-->>API: Error response
            API-->>Form: HTTP error status
            Form->>Modal: Set root error message
            Modal->>User: Display error
        else Upstream success
            Upstream-->>API: Order created
            API-->>Form: HTTP 201 + response JSON
            Form->>Modal: Call onCreated callback
            Modal->>Grid: onCreated triggered
            Grid->>Router: router.refresh()
            Router->>Grid: Reload order data
            Modal->>User: Close modal
        end
    end
Loading
sequenceDiagram
    participant User
    participant Toolbar as OrdersGridToolbar
    participant FiltersBar as OrdersGridFiltersBar
    participant Controller as useFiltersBarController
    participant Router as router.push()

    User->>Toolbar: Click "Filtro" button
    Toolbar->>FiltersBar: Display filter form
    User->>FiltersBar: Select "Venda" option
    FiltersBar->>Controller: setFieldValue('side', 'Venda')
    Controller->>Controller: Update formState.side
    User->>FiltersBar: Click "Aplicar"
    FiltersBar->>Controller: applyFilters(event)
    Controller->>Controller: Extract filter values from formState
    Controller->>Controller: Build URL search params
    Controller->>Controller: Reset page to 1
    Controller->>Router: router.push(url with updated filters)
    Router->>User: Navigate with new filter query params
    
    alt User clicks "Limpar"
        User->>FiltersBar: Click "Limpar"
        FiltersBar->>Controller: clearFilters()
        Controller->>Controller: Reset formState to empty
        Controller->>Controller: Remove all filter params
        Controller->>Router: router.push(url without filters)
        Router->>User: Navigate with cleared filters
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰 A table springs forth with columns bright,
Modal forms dance in the filter's light,
Date pickers bloom, inputs align,
Orders created in UTC time—
The rabbit hops through Storybook's delight! 📚✨

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch create-order

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 15

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🟡 Minor comments (9)
docs/patterns/components.md-217-241 (1)

217-241: ⚠️ Potential issue | 🟡 Minor

Fix section hierarchy/numbering for Zustand guidance.

## 10) State Management (Zustand) is followed by ## 11) When to use Zustand and then ## When NOT to use Zustand (unnumbered). Make these heading levels/numbering consistent for readability and navigation.

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

In `@docs/patterns/components.md` around lines 217 - 241, The headings under "##
10) State Management (Zustand)" are inconsistent—"## 11) When to use Zustand
(✅)" and an unnumbered "## When NOT to use Zustand" break the hierarchy; update
them to be consistent subheadings (e.g., change "## 11) When to use Zustand (✅)"
to "### When to use Zustand (✅)" and change "## When NOT to use Zustand" to "###
When NOT to use Zustand (❌)" or alternatively renumber as "10.1" / "10.2" if you
prefer numeric subsections so that the section titles (State Management
(Zustand), When to use Zustand, When NOT to use Zustand) follow a consistent
heading level and numbering scheme.
components/OrderGrid-bkp/order-grid-empty-state.ts-6-12 (1)

6-12: ⚠️ Potential issue | 🟡 Minor

User-facing Portuguese copy has missing accents.

At Line 7 and Line 11, "nao" should be "não" for correct PT-BR text quality.

✍️ Proposed fix
-		return `${filters.instrument} nao encontrado.`;
+		return `${filters.instrument} não encontrado.`;
...
-		return `ID ${filters.id} nao encontrado.`;
+		return `ID ${filters.id} não encontrado.`;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/OrderGrid-bkp/order-grid-empty-state.ts` around lines 6 - 12,
Update the Portuguese messages that use "nao" to the correct accented form "não"
in the return strings for the branches checking filters.instrument and
filters.id (i.e., the conditional blocks referencing filters.instrument and
filters.id), and ensure the file is saved with UTF-8 encoding so the "ã"
character is preserved.
components/ui/inputs/TextInput/TextInput.stories.tsx-32-36 (1)

32-36: ⚠️ Potential issue | 🟡 Minor

Fix Portuguese copy accents in user-facing story text.

"Campo obrigatorio" and "Entrada invalida" should use proper accents.

Suggested copy update
 export const Invalid: Story = {
 	args: {
-		errorMessage: 'Campo obrigatorio',
+		errorMessage: 'Campo obrigatório',
 		invalid: true,
 		label: 'Instrumento',
-		value: 'Entrada invalida',
+		value: 'Entrada inválida',
 	},
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ui/inputs/TextInput/TextInput.stories.tsx` around lines 32 - 36,
The story contains Portuguese strings with missing accents: update the
user-facing props in TextInput.stories.tsx (specifically the errorMessage and
value fields) to use proper accents — change errorMessage from "Campo
obrigatorio" to "Campo obrigatório" and value from "Entrada invalida" to
"Entrada inválida"; verify the label "Instrumento" is correct and adjust any
other Portuguese strings in the same story if needed.
components/OrderGrid/components/ActionButton.tsx-16-23 (1)

16-23: ⚠️ Potential issue | 🟡 Minor

Avoid always setting aria-pressed on non-toggle actions.

aria-pressed is currently always present because isActive defaults to false. That can incorrectly announce non-toggle buttons as toggle buttons.

✅ Suggested fix
 export const OrdersGridActionButton = ({
 	label,
 	iconAlt,
 	iconSrc,
 	onClick,
-	isActive = false,
+	isActive,
 }: OrdersGridActionButtonProps) => {
 	return (
 		<ButtonIcon
 			aria-label={label}
-			aria-pressed={isActive}
+			aria-pressed={typeof isActive === 'boolean' ? isActive : undefined}
 			className={`orders-grid__action-button ${
 				isActive ? 'orders-grid__action-button--active' : ''
 			}`.trim()}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/OrderGrid/components/ActionButton.tsx` around lines 16 - 23, The
component always renders aria-pressed because isActive defaults to false; change
OrdersGridActionButtonProps and the ActionButton component so isActive is
optional (no default false) and only pass aria-pressed to ButtonIcon when the
action is a toggle: conditionally include aria-pressed (e.g., only set
aria-pressed={isActive} when isActive is not undefined or when a new isToggle
prop is true). Update the ButtonIcon prop usage in ActionButton to omit
aria-pressed for non-toggle actions so non-toggle buttons are not announced as
toggle controls.
components/OrderGrid/components/Filter/Filter.styles.css-27-40 (1)

27-40: ⚠️ Potential issue | 🟡 Minor

Add explicit keyboard focus styling for text inputs to match select/date inputs.

The text input does not have explicit :focus/:focus-visible styles in this file, while select and date inputs do. Although global styles provide a :focus-visible outline, the text input will show a different visual appearance (the default outline) compared to the green outline used by select/date inputs. Adding explicit styles ensures consistent focus styling across all filter input types.

Suggested patch
 .orders-grid__filter-select-input:focus,
 .orders-grid__filter-select-input:focus-visible,
 .orders-grid__filter-date-input:focus,
-.orders-grid__filter-date-input:focus-visible {
+.orders-grid__filter-date-input:focus-visible,
+.orders-grid__filter-field .ui-text-input__input:focus,
+.orders-grid__filter-field .ui-text-input__input:focus-visible {
 	`@apply` outline-2 outline-green;
 	outline-offset: 2px;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/OrderGrid/components/Filter/Filter.styles.css` around lines 27 -
40, The text input selector ".orders-grid__filter-field .ui-text-input__input"
lacks explicit :focus/:focus-visible rules like the select/date inputs; update
the CSS so ".orders-grid__filter-field .ui-text-input__input" receives the same
focus styles as ".orders-grid__filter-select-input" and
".orders-grid__filter-date-input" by adding matching :focus and :focus-visible
rules that apply the same outline (outline-2 outline-green) and outline-offset:
2px to ensure consistent keyboard focus styling.
components/CreateOrderModal/index.tsx-51-59 (1)

51-59: ⚠️ Potential issue | 🟡 Minor

Reset form state on every modal close path, not only Cancel/success.
At Line 98, onOpenChange is passed directly to Modal.Root; closing via overlay/Escape can leave stale values/errors for the next open.

Suggested fix
 const CreateOrderModal = ({
 	open,
 	onOpenChange,
 	onCreated,
 }: CreateOrderModalProps) => {
+	const resetToDefaults = () =>
+		reset({
+			instrument: '',
+			side: 'Compra',
+			price: 0,
+			quantity: 0,
+		} as CreateOrderFormDefaults);
+
 	const closeModal = () => {
-		reset({
-			instrument: '',
-			side: 'Compra',
-			price: 0,
-			quantity: 0,
-		} as CreateOrderFormDefaults);
+		resetToDefaults();
 		onOpenChange(false);
 	};
+
+	const handleOpenChange = (nextOpen: boolean) => {
+		if (!nextOpen) resetToDefaults();
+		onOpenChange(nextOpen);
+	};

 	return (
-		<Modal.Root open={open} onOpenChange={onOpenChange}>
+		<Modal.Root open={open} onOpenChange={handleOpenChange}>

Also applies to: 98-98

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

In `@components/CreateOrderModal/index.tsx` around lines 51 - 59, The modal
currently passes onOpenChange directly to Modal.Root, so closing via
overlay/Escape skips your reset logic; create a wrapper handler (e.g.,
handleOpenChange) that accepts the new open state, and when it becomes false
call the existing closeModal (which resets the form and calls
onOpenChange(false)), otherwise forward true to onOpenChange; replace the direct
use of onOpenChange on Modal.Root with this wrapper so all close paths (Cancel,
success, overlay, Escape) run the same reset logic.
components/OrderGrid/components/Filter/useFiltersBarController.ts-46-55 (1)

46-55: ⚠️ Potential issue | 🟡 Minor

Form state may become stale if filters prop changes.

The formState is initialized once from filters via useState. If the filters prop changes (e.g., via browser back/forward navigation), the form inputs will show stale values. Consider using a useEffect to sync or derive state directly from props.

🔧 Proposed fix to sync form state with filter prop changes
 export const useFiltersBarController = ({
 	filters,
 	sortState,
 }: UseFiltersBarControllerArgs) => {
 	const router = useRouter();
 	const pathname = usePathname();
 	const searchParams = useSearchParams();
 	const [formState, setFormState] = useState<FiltersFormState>(
 		toFiltersFormState(filters),
 	);
+
+	// Sync form state when filters change (e.g., browser navigation)
+	useEffect(() => {
+		setFormState(toFiltersFormState(filters));
+	}, [filters]);

Also add the import:

-import { useState } from 'react';
+import { useState, useEffect } from 'react';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/OrderGrid/components/Filter/useFiltersBarController.ts` around
lines 46 - 55, useFiltersBarController initializes formState from filters once,
which can become stale when filters prop changes; add a useEffect inside
useFiltersBarController that watches filters and updates formState by calling
setFormState(toFiltersFormState(filters)) so the form reflects prop changes
(reference: useFiltersBarController, formState, setFormState,
toFiltersFormState, filters); ensure you import useEffect if not already
imported.
components/ui/inputs/SelectInput/SelectInput.stories.tsx-36-43 (1)

36-43: ⚠️ Potential issue | 🟡 Minor

Typo in user-facing text.

The error message 'Campo obrigatorio' is missing the accent mark. The correct Portuguese spelling is 'Campo obrigatório'.

✏️ Suggested fix
 export const Invalid: Story = {
 	args: {
-		errorMessage: 'Campo obrigatorio',
+		errorMessage: 'Campo obrigatório',
 		invalid: true,
 		label: 'Lado',
 		value: '',
 	},
 };

 export const Composed: Story = {
 	render: (args) => (
 		<SelectInput.Root {...args}>
 			<SelectInput.Label />
 			<SelectInput.Trigger />
 			<SelectInput.Menu />
 			<SelectInput.Error />
 		</SelectInput.Root>
 	),
 	args: {
 		label: 'Lado',
-		errorMessage: 'Campo obrigatorio',
+		errorMessage: 'Campo obrigatório',
 		invalid: true,
 		value: 'Compra',
 	},
 };

Also applies to: 54-59

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

In `@components/ui/inputs/SelectInput/SelectInput.stories.tsx` around lines 36 -
43, Update the user-facing typo in the story exports so the Portuguese error
message includes the accent: change the errorMessage value from 'Campo
obrigatorio' to 'Campo obrigatório' in the SelectInput.stories.tsx story export
named Invalid and also fix the other occurrence noted (the second story block
around lines 54-59) so both errorMessage strings use 'Campo obrigatório'.
components/OrderGrid/components/Filter/FilterSelect.tsx-57-101 (1)

57-101: ⚠️ Potential issue | 🟡 Minor

The ARIA roles don't match the interaction model.

This popup advertises a listbox, but each option contains a nested <button> and there is no listbox keyboard handling. Assistive tech will get listbox semantics that the widget doesn't actually support. Either implement a real listbox pattern here or drop the listbox/option roles and treat it as a regular popup list.

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

In `@components/OrderGrid/components/Filter/FilterSelect.tsx` around lines 57 -
101, The component currently advertises a listbox but renders each choice as a
nested <button> without listbox keyboard handling (see isOpen, hasOptions,
options.map, selectedOption, effectiveValue, onChange, setIsOpen), which
misleads assistive tech; fix by dropping the listbox/option roles and using
proper popup semantics instead—change the <ul aria-labelledby={id}
role="listbox"> to a popup/menu role (e.g., role="menu" or remove role) and
change each option element to use role="menuitem" (or leave as native <button>
without option role), ensure aria-haspopup and aria-expanded remain on the
trigger and keep the existing onClick handlers (onChange and setIsOpen) intact
so behavior doesn't change.
🧹 Nitpick comments (16)
styles/theme.css (1)

33-33: Token duplication can drift; alias the value instead.

Line 33 duplicates Line 32’s clamp value. Consider mapping --text-size-16 to --font-size-base to keep a single source of truth.

♻️ Proposed refactor
-	--text-size-16: clamp(1.1rem, 1rem + 0.5vw, 1.6rem);
+	--text-size-16: var(--font-size-base);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@styles/theme.css` at line 33, The CSS variable --text-size-16 duplicates the
clamp expression used elsewhere; change it to reference the canonical token
--font-size-base instead (i.e., set --text-size-16 to var(--font-size-base)) so
the value is aliased to a single source of truth and future drift is prevented;
update any usages relying on --text-size-16 accordingly to inherit the
centralized value.
components/CreateOrderModal/CreateOrderModal.styles.css (1)

24-30: Button width/margin styles are duplicated.

Line 24–30 can be merged into a shared selector to reduce repetition.

♻️ Proposed refactor
-.create-order-modal__submit-button {
-	`@apply` w-[11.6rem] mt-4 self-start;
-}
-
-.create-order-modal__cancel-button {
+.create-order-modal__submit-button,
+.create-order-modal__cancel-button {
 	`@apply` w-[11.6rem] mt-4 self-start;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/CreateOrderModal/CreateOrderModal.styles.css` around lines 24 -
30, The two duplicated rules for .create-order-modal__submit-button and
.create-order-modal__cancel-button should be merged into a single, shared
selector to avoid repetition; update the CSS by combining the selectors (e.g.,
".create-order-modal__submit-button, .create-order-modal__cancel-button") and
keep the `@apply` w-[11.6rem] mt-4 self-start declaration in that single rule so
both buttons inherit the same width and margin.
components/ui/inputs/DatePickerInput/DatePickerInput.styles.css (1)

82-84: Inconsistent color usage for error state.

Line 79 uses the custom border-red-alert color, but line 83 uses the default Tailwind text-red-600. Consider using a consistent custom color token for error states.

♻️ Use consistent error color
 .ui-date-picker-input__error {
-	`@apply` text-grid-sm text-red-600 font-semibold;
+	`@apply` text-grid-sm text-red-alert font-semibold;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ui/inputs/DatePickerInput/DatePickerInput.styles.css` around lines
82 - 84, The error text class .ui-date-picker-input__error uses the default
Tailwind color text-red-600 while the component uses the custom token
border-red-alert elsewhere; update .ui-date-picker-input__error to use the same
custom token (e.g., replace text-red-600 with text-red-alert or the project’s
text-red-alert utility) so error text and border colors are consistent across
the DatePickerInput styles.
components/OrderGrid-bkp/OrderGrid.styles.css (1)

1-5: Consider removing backup stylesheet after migration is complete.

The -bkp suffix indicates this is a legacy/backup file. The AI summary mentions this overlaps with the newer non-bkp implementation. Consider removing this file once the migration to the new OrderGrid component is verified and stable.

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

In `@components/OrderGrid-bkp/OrderGrid.styles.css` around lines 1 - 5, This
backup stylesheet (components/OrderGrid-bkp/OrderGrid.styles.css) with the -bkp
suffix and the .orders-grid rule appears to overlap the migrated OrderGrid
styles; remove this legacy file once the new OrderGrid implementation is
verified stable, and ensure no imports reference
components/OrderGrid-bkp/OrderGrid.styles.css (search for any imports of
OrderGrid-bkp or .orders-grid); if any remain, update them to point to the new
OrderGrid styles or component to avoid broken imports.
lib/orders/create-order.schema.ts (1)

10-17: Contradictory validation pattern for side field.

The combination of .enum(), .optional(), and .refine() creates confusing validation logic. The .optional() allows undefined, but the subsequent .refine() will reject undefined since it doesn't equal 'Compra' or 'Venda'. If side is required, remove .optional():

♻️ Simplified required enum validation
 	side: z
 		.enum(['Compra', 'Venda'], {
 			error: 'Lado e obrigatorio',
-		})
-		.optional()
-		.refine((val) => val === 'Compra' || val === 'Venda', {
-			message: 'Lado e obrigatorio',
 		}),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/orders/create-order.schema.ts` around lines 10 - 17, The `side` schema
currently mixes .enum(), .optional(), and .refine(), causing contradictory
validation: .optional() allows undefined but .refine() rejects it. Fix by making
`side` required: remove .optional() and also remove the redundant .refine()
(z.enum(['Compra','Venda'], { error: 'Lado e obrigatorio' }) is sufficient) so
validation is consistent; if instead you intend `side` to be optional, keep
.optional() and change the .refine() predicate to allow undefined (e.g., val ===
undefined || val === 'Compra' || val === 'Venda').
components/ui/buttons/ButtonIcon/ButtonIcon.styles.css (1)

23-45: Consolidate duplicated primary/secondary modifier rules.

Line 23 through Line 45 repeats the same declarations for both variants; collapsing this will reduce maintenance drift risk.

♻️ Suggested refactor
-.ui-button-icon--primary {
-	`@apply` font-semibold;
-}
-
-.ui-button-icon--primary:hover {
-	`@apply` opacity-80;
-}
-
-.ui-button-icon--primary:active {
-	`@apply` opacity-70;
-}
-
-.ui-button-icon--secondary {
+.ui-button-icon--primary,
+.ui-button-icon--secondary {
 	`@apply` font-semibold;
 }
 
-.ui-button-icon--secondary:hover {
+.ui-button-icon--primary:hover,
+.ui-button-icon--secondary:hover {
 	`@apply` opacity-80;
 }
 
-.ui-button-icon--secondary:active {
+.ui-button-icon--primary:active,
+.ui-button-icon--secondary:active {
 	`@apply` opacity-70;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ui/buttons/ButtonIcon/ButtonIcon.styles.css` around lines 23 - 45,
The .ui-button-icon--primary and .ui-button-icon--secondary blocks duplicate
identical declarations (font-semibold, :hover opacity-80, :active opacity-70);
refactor by extracting the common styles into a shared selector (e.g.,
.ui-button-icon--primary, .ui-button-icon--secondary { ... }) and likewise
combine the hover and active state rules into shared selectors
(.ui-button-icon--primary:hover, .ui-button-icon--secondary:hover { ... } and
.ui-button-icon--primary:active, .ui-button-icon--secondary:active { ... }) so
the three duplicated rule sets are consolidated while preserving the same
Tailwind `@apply` utilities.
components/ui/Table/__tests__/Table.test.tsx (1)

51-56: Prefer a null-safe row assertion over as Element casting.

The cast can mask a null path and make failures less actionable.

Refactor suggestion
 		const firstRowCell = screen.getByText('PETR4');
 		const firstRow = firstRowCell.closest('tr');
-		expect(firstRow).toBeTruthy();
-
-		fireEvent.click(firstRow as Element);
-		fireEvent.keyDown(firstRow as Element, { key: 'Enter' });
+		expect(firstRow).not.toBeNull();
+		if (!firstRow) return;
+
+		fireEvent.click(firstRow);
+		fireEvent.keyDown(firstRow, { key: 'Enter' });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ui/Table/__tests__/Table.test.tsx` around lines 51 - 56, The test
currently casts firstRow with `as Element`, which can hide null issues; instead
assert the row exists using a null-safe assertion (e.g.,
`expect(firstRow).not.toBeNull()` or `expect(firstRow).toBeTruthy()`) right
after `const firstRow = firstRowCell.closest('tr')`, then call
`fireEvent.click(firstRow)` and `fireEvent.keyDown(firstRow, { key: 'Enter' })`
using the asserted `firstRow` (referencing `firstRowCell.closest('tr')`,
`firstRow`, and the `fireEvent.click` / `fireEvent.keyDown` calls) so the test
fails clearly if the row is missing rather than silently casting a null.
components/OrderGrid/components/Filter/FiltersBar.tsx (1)

23-26: Add useEffect to controller to sync form state when filters prop changes, then remove the key remount.

The JSON.stringify(filters) key currently forces component remount to reset form state when the filters prop changes. Instead, add a useEffect in useFiltersBarController to sync formState whenever the filters prop changes:

useEffect(() => {
  setFormState(toFiltersFormState(filters));
}, [filters]);

Then remove the key from the form element. This eliminates unnecessary remounts and avoids focus/state churn while maintaining proper synchronization.

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

In `@components/OrderGrid/components/Filter/FiltersBar.tsx` around lines 23 - 26,
The form is currently forced to remount via key={JSON.stringify(filters)};
instead update the controller to sync state on prop changes: inside
useFiltersBarController add a useEffect that calls
setFormState(toFiltersFormState(filters)) whenever the filters prop changes,
ensuring formState tracks filters; then remove the JSON.stringify(filters) key
from the <form> so the component no longer remounts while still keeping state in
sync.
components/OrderGrid-bkp/order-grid-with-pagination.presenter.ts (1)

26-33: Consider error handling for the data fetch.

If getPaginatedOrdersForGrid throws (e.g., network error, service unavailable), the error will bubble up unhandled. Depending on your error boundary setup, you may want to catch and transform this into a user-friendly error state within the view model.

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

In `@components/OrderGrid-bkp/order-grid-with-pagination.presenter.ts` around
lines 26 - 33, Wrap the call to getPaginatedOrdersForGrid(...) in a try/catch
inside the presenter (the code that builds the order grid view model in
order-grid-with-pagination.presenter.ts); on error catch the thrown exception
and either set a clear error state on the view model (e.g., errorMessage /
hasError) or return a safe fallback (empty items, currentPage=0, totalPages=0)
and log the original error for diagnostics, ensuring you include the original
parameters (requestedPage, pageSize, sortBy, sortDir, filters) in the log so the
failure can be reproduced.
components/ui/inputs/DatePickerInput/__tests__/DatePickerInput.test.tsx (1)

67-84: Consider adding a test for popover close after date selection.

The compound composition test validates error/invalid state rendering, but there's no explicit test verifying that selecting a date closes the popover. If that's expected behavior, adding a test would prevent regressions.

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

In `@components/ui/inputs/DatePickerInput/__tests__/DatePickerInput.test.tsx`
around lines 67 - 84, Add a test to verify the popover closes after selecting a
date: render the same compound composition (DatePickerInput.Root with
DatePickerInput.Trigger and DatePickerInput.Popover), open the popover by
interacting with DatePickerInput.Trigger (e.g., userEvent.click on the trigger),
simulate selecting a date inside DatePickerInput.Popover (click the date element
rendered by the calendar), and then assert the popover is no longer in the
document or not visible (e.g., expect(queryByRole('dialog' or appropriate
role)).toBeNull()/not.toBeInTheDocument()). Use the existing helpers like
screen/getByRole and userEvent to perform interactions and reference
DatePickerInput.Trigger and DatePickerInput.Popover in the test.
components/ui/Table/components/Body.tsx (1)

42-56: Consider adding role="row" or role="button" for accessible clickable rows.

When onRowClick is provided, the <tr> becomes interactive. For better screen reader support, consider adding role="button" (or keeping role="row" while adding aria-pressed or similar) so assistive technologies announce the element as actionable.

♿ Proposed accessibility enhancement
 <tr
 	className={rowClassName}
 	key={rowKey}
 	onClick={onRowClick ? () => onRowClick(row) : undefined}
 	onKeyDown={
 		onRowClick
 			? (event) => {
 					if (event.key === 'Enter' || event.key === ' ') {
 						event.preventDefault();
 						onRowClick(row);
 					}
 			  }
 			: undefined
 	}
 	tabIndex={onRowClick ? 0 : undefined}
+	role={onRowClick ? 'button' : undefined}
 >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ui/Table/components/Body.tsx` around lines 42 - 56, The clickable
table row rendered in Body.tsx becomes interactive when onRowClick is present
but lacks an accessibility role; update the <tr> element (where rowClassName,
rowKey and onRowClick are used) to include role="button" when onRowClick is
provided, add an appropriate aria-* state such as aria-pressed={false} or
aria-label if needed, and ensure tabIndex remains set to 0 and the existing
onKeyDown handler is kept so screen readers and keyboard users recognize the row
as actionable.
components/ui/buttons/Button/Button.styles.css (1)

27-49: Add disabled and focus-visible styles for accessibility and UX completeness.

The button variants define :hover and :active states but lack:

  1. :disabled state (to visually indicate non-interactivity)
  2. :focus-visible state (for keyboard navigation accessibility)
♿ Proposed additions for disabled and focus states
 .ui-button--primary:active {
 	`@apply` border-green-dark bg-green-dark text-white;
 }

+.ui-button--primary:disabled {
+	`@apply` opacity-50 cursor-not-allowed;
+}
+
+.ui-button--primary:focus-visible {
+	`@apply` outline-2 outline-offset-2 outline-green;
+}

 .ui-button--secondary {
 	`@apply` border-gray-dark bg-gray-dark text-white font-semibold;
 }
 
 .ui-button--secondary:hover {
 	`@apply` border-gray-darker bg-gray-darker text-white;
 }
 
 .ui-button--secondary:active {
 	`@apply` border-gray-darker bg-gray-darker text-white;
 }
+
+.ui-button--secondary:disabled {
+	`@apply` opacity-50 cursor-not-allowed;
+}
+
+.ui-button--secondary:focus-visible {
+	`@apply` outline-2 outline-offset-2 outline-gray-dark;
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/ui/buttons/Button/Button.styles.css` around lines 27 - 49, Add
explicit :disabled and :focus-visible rules for both .ui-button--primary and
.ui-button--secondary to improve accessibility: for :disabled apply
non-interactive visuals (muted background/border/text via existing utility
tokens, reduced opacity or specific bg/border text classes, and
cursor-not-allowed), and ensure hover/active styles do not override disabled;
for :focus-visible add a clear keyboard focus indicator (outline/ring or visible
border change using your design tokens) so keyboard users see focus on
.ui-button--primary and .ui-button--secondary. Reference the existing class
names (.ui-button--primary, .ui-button--secondary) and their :hover/:active
blocks when adding these new :disabled and :focus-visible rules so styles are
consistent and precedence prevents hover/active from applying when disabled.
components/ui/inputs/DatePickerInput/components/DatePickerInputPopover.tsx (1)

29-107: Consider focus management for the dialog popover.

The popover uses role="dialog" and properly closes on Escape key press. However, it lacks focus management: when the dialog opens, focus should move into it (typically to the first day button or clear button), and when it closes, focus should return to the trigger button. This improves keyboard navigation experience for users.

app/api/orders/route.ts (1)

80-92: Consider adding idempotency handling for order creation.

If the upstream POST succeeds but the response fails to reach the client (network issues, timeouts), a retry would create a duplicate order. Consider:

  • Accepting a client-generated idempotency key
  • Using clientOrderId as an idempotency key and checking for existing orders before creating
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/orders/route.ts` around lines 80 - 92, The POST handling currently
proxies order creation via fetchWithTimeout and will create duplicates on client
retry; add idempotency handling by accepting an Idempotency-Key header (or using
payload.clientOrderId) and checking for an existing order before creating:
extract the header or payload.clientOrderId in route.ts, query the upstream or
your DB for an existing order with that key, return the existing order if found,
otherwise include the Idempotency-Key when calling fetchWithTimeout (or persist
the key to map to the created order after success) and only create once; update
error/response flows around fetchWithTimeout and NextResponse.json to return the
original order when deduplicated.
components/ui/inputs/TextInput/TextInput.styles.css (1)

21-27: Inconsistent error color tokens.

The invalid border uses border-red-alert (line 22) while the error text uses text-red-600 (line 26). Consider using the same color token for both to maintain visual consistency in error states.

♻️ Suggested fix
 .ui-text-input__error {
-	`@apply` text-grid-sm text-red-600 font-semibold;
+	`@apply` text-grid-sm text-red-alert font-semibold;
 }

Or alternatively, update the invalid border to use border-red-600 if red-600 is the intended error color.

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

In `@components/ui/inputs/TextInput/TextInput.styles.css` around lines 21 - 27,
The error styles are inconsistent: .ui-text-input--invalid uses border-red-alert
while .ui-text-input__error uses text-red-600; pick one token and make both use
it for consistent error styling. Update either .ui-text-input--invalid to use
border-red-600 to match text-red-600, or change .ui-text-input__error to
text-red-alert so both classes (.ui-text-input--invalid and
.ui-text-input__error) use the same color token across border and text.
components/OrderGrid/components/Filter/filters.config.ts (1)

12-18: Reduce duplicated filter-shape declarations to prevent drift.

FILTER_FORM_KEYS, toFiltersFormState, and emptyFiltersFormState repeat the same field list. Consider deriving them from a single source constant.

♻️ Suggested refactor
+const EMPTY_FILTERS_FORM_STATE: FiltersFormState = {
+  id: '',
+  instrument: '',
+  status: '',
+  side: '',
+  date: '',
+};

-export const FILTER_FORM_KEYS: Array<keyof FiltersFormState> = [
-  'id',
-  'instrument',
-  'status',
-  'side',
-  'date',
-];
+export const FILTER_FORM_KEYS = Object.keys(
+  EMPTY_FILTERS_FORM_STATE,
+) as Array<keyof FiltersFormState>;

 export const toFiltersFormState = (
   filters: OrdersGridFilterState,
 ): FiltersFormState => ({
-  id: filters.id ?? '',
-  instrument: filters.instrument ?? '',
-  status: filters.status ?? '',
-  side: filters.side ?? '',
-  date: filters.date ?? '',
+  ...EMPTY_FILTERS_FORM_STATE,
+  id: filters.id ?? EMPTY_FILTERS_FORM_STATE.id,
+  instrument: filters.instrument ?? EMPTY_FILTERS_FORM_STATE.instrument,
+  status: filters.status ?? EMPTY_FILTERS_FORM_STATE.status,
+  side: filters.side ?? EMPTY_FILTERS_FORM_STATE.side,
+  date: filters.date ?? EMPTY_FILTERS_FORM_STATE.date,
 });

-export const emptyFiltersFormState = (): FiltersFormState => ({
-  id: '',
-  instrument: '',
-  status: '',
-  side: '',
-  date: '',
-});
+export const emptyFiltersFormState = (): FiltersFormState => ({
+  ...EMPTY_FILTERS_FORM_STATE,
+});

Also applies to: 86-102

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

In `@components/OrderGrid/components/Filter/filters.config.ts` around lines 12 -
18, FILTER_FORM_KEYS, toFiltersFormState, and emptyFiltersFormState duplicate
the same field list and should be derived from one source to avoid drift; create
a single constant (e.g., FILTER_FIELD_NAMES or FILTER_FORM_SCHEMA) that lists
the keys and then build FILTER_FORM_KEYS from that constant and implement
toFiltersFormState and emptyFiltersFormState by mapping over that same constant
so all three always come from the same source; update any type assertions or
casts in toFiltersFormState/emptyFiltersFormState to use the new constant and
adjust usages of FILTER_FORM_KEYS to reference the derived value.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/api/orders/route.ts`:
- Around line 12-32: getNextOrderId() computes a sequential ID by reading
existing orders, which risks race conditions when multiple requests create
orders concurrently; update the create-order flow to stop relying on that
function for unique IDs by either delegating ID generation to the upstream API
or generating a collision-resistant ID locally (e.g., crypto.randomUUID()) and
stop calling getNextOrderId() in the POST handler, and if you keep client-side
IDs add an idempotency/retry strategy: catch conflict responses (HTTP 409) from
the POST, regenerate a new UUID (or re-request the upstream-generated ID) and
retry a limited number of times; refer to getNextOrderId and the POST request
handling code where the new order is posted to implement these changes.

In `@components/CreateOrderModal/__tests__/CreateOrderModal.test.tsx`:
- Around line 46-47: The test overrides global.fetch with
jest.fn().mockResolvedValue(...) which can leak into other tests; capture the
original global.fetch at the start of the test file (e.g., const originalFetch =
global.fetch) and restore it in an afterEach/afterAll hook, or call global.fetch
= originalFetch after the test that sets global.fetch, ensuring the mocked
jest.fn() used in CreateOrderModal.test.tsx is cleaned up and the original fetch
is reinstated.

In `@components/CreateOrderModal/index.tsx`:
- Around line 80-91: The fetch call that posts to '/api/orders' can throw
network errors which are not currently caught; wrap the await fetch(...) in a
try/catch (around the code in CreateOrderModal/index.tsx where you call fetch)
and in the catch block call setError('root', { message: 'Nao foi possivel criar
a ordem. Verifique sua conexao e tente novamente.' }) (or include the caught
error.message for more detail), then return to prevent further processing; keep
the existing response.ok handling inside the try.

In `@components/OrderGrid-bkp/order-grid-with-pagination.presenter.ts`:
- Around line 1-54: The `components/OrderGrid-bkp/` backup directory (e.g., file
OrderGrid-bkp/order-grid-with-pagination.presenter.ts and related files) is dead
code and should be removed before merging; delete the entire OrderGrid-bkp
folder and ensure no imports reference it (search for any usages of
OrderGrid-bkp or its exported symbols like
buildOrderGridWithPaginationViewModel) so the repo only contains the active
components in components/OrderGrid/.

In `@components/OrderGrid-bkp/useOrderGridWithPaginationController.ts`:
- Around line 8-11: Remove the dataToken symbol from the selection machinery:
change the SelectionState type to only include orderId and update all places
that create, set, compare, or validate SelectionState (references around the
SelectionState declaration and the blocks noted at lines 22-37 and 58-62) to
stop generating or checking dataToken; instead resolve selection solely by
orderId against the current orders array (e.g., in the selectedOrder resolver
and any setSelectedOrder logic) so that harmless rerenders which recreate
Symbols no longer clear the selection.

In `@components/OrderGrid/components/Filter/FilterDatePicker.tsx`:
- Around line 12-81: Remove the duplicated declarations (CalendarCell,
WEEK_DAYS, parseIsoDate, toIsoDate, formatInputDate, getMonthLabel,
buildCalendarCells) from FilterDatePicker.tsx and replace them with imports from
the shared date-picker utilities module; ensure you import the exact symbols
(CalendarCell, WEEK_DAYS, parseIsoDate, toIsoDate, formatInputDate,
getMonthLabel, buildCalendarCells) and update any references in FilterDatePicker
to use the imported symbols, adding/adjusting exports in the shared utils if any
symbol is not yet exported.

In `@components/ui/inputs/RadioInput/index.tsx`:
- Around line 49-56: The visible label element (class "ui-radio-input__label")
isn't associated with the radiogroup and the group can be unnamed when both
label and aria-label are missing; update the RadioInput component to
generate/accept a stable id for the label (e.g., `${id}-label`), give the <p
className="ui-radio-input__label"> that id when label exists, and
replace/augment the radiogroup attributes to use aria-labelledby={labelId} when
label exists (falling back to aria-label={ariaLabel || fallbackLabel} when it
doesn't) so the radiogroup always has an accessible name and the visible label
is linked via aria-labelledby.

In `@components/ui/inputs/SelectInput/components/SelectInputRoot.tsx`:
- Around line 75-79: The hidden input in SelectInputRoot is always a successful
form control even when the trigger is disabled; update the hidden input
rendering so it mirrors the disabled state (e.g., add disabled={disabled} or
disabled={contextValue.disabled} to the <input name={name} type="hidden"
value={selectedValue} />) so a visually disabled select does not submit a value.

In `@components/ui/inputs/SelectInput/SelectInput.styles.css`:
- Around line 47-49: The invalid state currently applies border-red-alert to the
root wrapper (.ui-select-input--invalid) but the visible border is on the inner
element .ui-select-input__field; update the stylesheet so that when the root has
the invalid class the border-red-alert is applied to .ui-select-input__field
(e.g., use a selector like .ui-select-input--invalid .ui-select-input__field) or
duplicate the border rule on .ui-select-input__field to ensure the visible field
shows the invalid border.

In `@components/ui/inputs/SelectInput/useSelectInputController.ts`:
- Around line 21-31: The current logic lets selectedValue keep a missing value
even after selectedOption falls back to options[0], causing the visible label
and the submitted value to diverge; to fix, first derive a resolvedValue (use
isControlled ? String(value ?? '') : internalValue), then compute selectedOption
via useMemo(() => options.find(o => o.value === resolvedValue) ?? options[0],
[options, resolvedValue]), and finally normalize selectedValue to
selectedOption.value (e.g., const selectedValue = selectedOption?.value ?? ''),
updating references to initialValue, internalValue, selectedValue and
selectedOption accordingly so the visible option and the actual value always
match.

In `@docs/patterns/components.md`:
- Around line 21-24: Update the guidance in docs/patterns/components.md to align
with Next.js colocation best practices: remove the "must contain **only** route
files" wording and instead state that route files (page.tsx, layout.tsx,
loading.tsx, error.tsx, not-found.tsx, route.ts) define routes while
non‑routable utilities may be colocated in app/ using private folders (example:
app/blog/_components/Post.tsx) or kept outside app/ for shared UI logic to avoid
app-router coupling; mention using leading-underscore folders (e.g.,
_components/) for private utilities to prevent them from becoming routes.
- Around line 67-73: Update the "Rule: no Tailwind utility strings inline in
JSX" guidance to align with Tailwind v4 best practices: change the rule text in
the "no Tailwind utility strings inline in JSX" section to recommend using
inline Tailwind utility classes in JSX (e.g., className="flex gap-2 ...") as the
default pattern for component styling, and state that component-scoped CSS
(.styles.css and `@apply`) should be reserved for third-party overrides or truly
custom CSS needs; update examples and the bullet list to show inline utilities
as preferred, keep semantic className usage as an option for larger structural
hooks, and explicitly document the exception criteria where `@apply` or CSS
modules are still appropriate.

In `@package.json`:
- Around line 25-30: The package "storybook" is currently listed in dependencies
but is a development-only tool; move the "storybook": "^10.3.1" entry out of the
top-level "dependencies" block and add it to the "devDependencies" block (where
"@storybook/nextjs" already lives) so it is installed only in dev environments;
ensure you remove the original "storybook" entry from dependencies and keep the
same version string when adding it to devDependencies.

In `@server/db.json`:
- Around line 2-1048: The seeded orders array returns two different shapes
because only recent entries include the fields clientOrderId, exchangeOrderId,
and executionId; fix by making the order shape uniform: either backfill every
object in the /orders collection to include clientOrderId, exchangeOrderId, and
executionId (set to null or empty string) so all fixtures share the same keys,
or update the Order type/schema and any serializers/parsers (the order consumer,
order creation flow, and any presenters reading /orders) to mark clientOrderId,
exchangeOrderId, and executionId as optional and handle their absence
consistently across reads/writes; pick one approach and apply it consistently to
the seeded data and to the Order contract used by the API/clients.

In `@styles/globals.css`:
- Around line 4-5: Remove the external `@import` line from globals.css and instead
self-host the font using next/font/local: add the font files under public/fonts
(e.g., WixMadeforText-Regular.woff2), create a localFont import in your Next.js
root layout or _app (using next/font/local with weight/style and preload),
export its className or CSS variable, and apply that className/variable to the
html/body element (and delete the original `@import` in styles/globals.css);
ensure you configure fallback fonts and any required subsets in the localFont
call to retain optimization and CLS prevention.

---

Minor comments:
In `@components/CreateOrderModal/index.tsx`:
- Around line 51-59: The modal currently passes onOpenChange directly to
Modal.Root, so closing via overlay/Escape skips your reset logic; create a
wrapper handler (e.g., handleOpenChange) that accepts the new open state, and
when it becomes false call the existing closeModal (which resets the form and
calls onOpenChange(false)), otherwise forward true to onOpenChange; replace the
direct use of onOpenChange on Modal.Root with this wrapper so all close paths
(Cancel, success, overlay, Escape) run the same reset logic.

In `@components/OrderGrid-bkp/order-grid-empty-state.ts`:
- Around line 6-12: Update the Portuguese messages that use "nao" to the correct
accented form "não" in the return strings for the branches checking
filters.instrument and filters.id (i.e., the conditional blocks referencing
filters.instrument and filters.id), and ensure the file is saved with UTF-8
encoding so the "ã" character is preserved.

In `@components/OrderGrid/components/ActionButton.tsx`:
- Around line 16-23: The component always renders aria-pressed because isActive
defaults to false; change OrdersGridActionButtonProps and the ActionButton
component so isActive is optional (no default false) and only pass aria-pressed
to ButtonIcon when the action is a toggle: conditionally include aria-pressed
(e.g., only set aria-pressed={isActive} when isActive is not undefined or when a
new isToggle prop is true). Update the ButtonIcon prop usage in ActionButton to
omit aria-pressed for non-toggle actions so non-toggle buttons are not announced
as toggle controls.

In `@components/OrderGrid/components/Filter/Filter.styles.css`:
- Around line 27-40: The text input selector ".orders-grid__filter-field
.ui-text-input__input" lacks explicit :focus/:focus-visible rules like the
select/date inputs; update the CSS so ".orders-grid__filter-field
.ui-text-input__input" receives the same focus styles as
".orders-grid__filter-select-input" and ".orders-grid__filter-date-input" by
adding matching :focus and :focus-visible rules that apply the same outline
(outline-2 outline-green) and outline-offset: 2px to ensure consistent keyboard
focus styling.

In `@components/OrderGrid/components/Filter/FilterSelect.tsx`:
- Around line 57-101: The component currently advertises a listbox but renders
each choice as a nested <button> without listbox keyboard handling (see isOpen,
hasOptions, options.map, selectedOption, effectiveValue, onChange, setIsOpen),
which misleads assistive tech; fix by dropping the listbox/option roles and
using proper popup semantics instead—change the <ul aria-labelledby={id}
role="listbox"> to a popup/menu role (e.g., role="menu" or remove role) and
change each option element to use role="menuitem" (or leave as native <button>
without option role), ensure aria-haspopup and aria-expanded remain on the
trigger and keep the existing onClick handlers (onChange and setIsOpen) intact
so behavior doesn't change.

In `@components/OrderGrid/components/Filter/useFiltersBarController.ts`:
- Around line 46-55: useFiltersBarController initializes formState from filters
once, which can become stale when filters prop changes; add a useEffect inside
useFiltersBarController that watches filters and updates formState by calling
setFormState(toFiltersFormState(filters)) so the form reflects prop changes
(reference: useFiltersBarController, formState, setFormState,
toFiltersFormState, filters); ensure you import useEffect if not already
imported.

In `@components/ui/inputs/SelectInput/SelectInput.stories.tsx`:
- Around line 36-43: Update the user-facing typo in the story exports so the
Portuguese error message includes the accent: change the errorMessage value from
'Campo obrigatorio' to 'Campo obrigatório' in the SelectInput.stories.tsx story
export named Invalid and also fix the other occurrence noted (the second story
block around lines 54-59) so both errorMessage strings use 'Campo obrigatório'.

In `@components/ui/inputs/TextInput/TextInput.stories.tsx`:
- Around line 32-36: The story contains Portuguese strings with missing accents:
update the user-facing props in TextInput.stories.tsx (specifically the
errorMessage and value fields) to use proper accents — change errorMessage from
"Campo obrigatorio" to "Campo obrigatório" and value from "Entrada invalida" to
"Entrada inválida"; verify the label "Instrumento" is correct and adjust any
other Portuguese strings in the same story if needed.

In `@docs/patterns/components.md`:
- Around line 217-241: The headings under "## 10) State Management (Zustand)"
are inconsistent—"## 11) When to use Zustand (✅)" and an unnumbered "## When NOT
to use Zustand" break the hierarchy; update them to be consistent subheadings
(e.g., change "## 11) When to use Zustand (✅)" to "### When to use Zustand (✅)"
and change "## When NOT to use Zustand" to "### When NOT to use Zustand (❌)" or
alternatively renumber as "10.1" / "10.2" if you prefer numeric subsections so
that the section titles (State Management (Zustand), When to use Zustand, When
NOT to use Zustand) follow a consistent heading level and numbering scheme.

---

Nitpick comments:
In `@app/api/orders/route.ts`:
- Around line 80-92: The POST handling currently proxies order creation via
fetchWithTimeout and will create duplicates on client retry; add idempotency
handling by accepting an Idempotency-Key header (or using payload.clientOrderId)
and checking for an existing order before creating: extract the header or
payload.clientOrderId in route.ts, query the upstream or your DB for an existing
order with that key, return the existing order if found, otherwise include the
Idempotency-Key when calling fetchWithTimeout (or persist the key to map to the
created order after success) and only create once; update error/response flows
around fetchWithTimeout and NextResponse.json to return the original order when
deduplicated.

In `@components/CreateOrderModal/CreateOrderModal.styles.css`:
- Around line 24-30: The two duplicated rules for
.create-order-modal__submit-button and .create-order-modal__cancel-button should
be merged into a single, shared selector to avoid repetition; update the CSS by
combining the selectors (e.g., ".create-order-modal__submit-button,
.create-order-modal__cancel-button") and keep the `@apply` w-[11.6rem] mt-4
self-start declaration in that single rule so both buttons inherit the same
width and margin.

In `@components/OrderGrid-bkp/order-grid-with-pagination.presenter.ts`:
- Around line 26-33: Wrap the call to getPaginatedOrdersForGrid(...) in a
try/catch inside the presenter (the code that builds the order grid view model
in order-grid-with-pagination.presenter.ts); on error catch the thrown exception
and either set a clear error state on the view model (e.g., errorMessage /
hasError) or return a safe fallback (empty items, currentPage=0, totalPages=0)
and log the original error for diagnostics, ensuring you include the original
parameters (requestedPage, pageSize, sortBy, sortDir, filters) in the log so the
failure can be reproduced.

In `@components/OrderGrid-bkp/OrderGrid.styles.css`:
- Around line 1-5: This backup stylesheet
(components/OrderGrid-bkp/OrderGrid.styles.css) with the -bkp suffix and the
.orders-grid rule appears to overlap the migrated OrderGrid styles; remove this
legacy file once the new OrderGrid implementation is verified stable, and ensure
no imports reference components/OrderGrid-bkp/OrderGrid.styles.css (search for
any imports of OrderGrid-bkp or .orders-grid); if any remain, update them to
point to the new OrderGrid styles or component to avoid broken imports.

In `@components/OrderGrid/components/Filter/filters.config.ts`:
- Around line 12-18: FILTER_FORM_KEYS, toFiltersFormState, and
emptyFiltersFormState duplicate the same field list and should be derived from
one source to avoid drift; create a single constant (e.g., FILTER_FIELD_NAMES or
FILTER_FORM_SCHEMA) that lists the keys and then build FILTER_FORM_KEYS from
that constant and implement toFiltersFormState and emptyFiltersFormState by
mapping over that same constant so all three always come from the same source;
update any type assertions or casts in toFiltersFormState/emptyFiltersFormState
to use the new constant and adjust usages of FILTER_FORM_KEYS to reference the
derived value.

In `@components/OrderGrid/components/Filter/FiltersBar.tsx`:
- Around line 23-26: The form is currently forced to remount via
key={JSON.stringify(filters)}; instead update the controller to sync state on
prop changes: inside useFiltersBarController add a useEffect that calls
setFormState(toFiltersFormState(filters)) whenever the filters prop changes,
ensuring formState tracks filters; then remove the JSON.stringify(filters) key
from the <form> so the component no longer remounts while still keeping state in
sync.

In `@components/ui/buttons/Button/Button.styles.css`:
- Around line 27-49: Add explicit :disabled and :focus-visible rules for both
.ui-button--primary and .ui-button--secondary to improve accessibility: for
:disabled apply non-interactive visuals (muted background/border/text via
existing utility tokens, reduced opacity or specific bg/border text classes, and
cursor-not-allowed), and ensure hover/active styles do not override disabled;
for :focus-visible add a clear keyboard focus indicator (outline/ring or visible
border change using your design tokens) so keyboard users see focus on
.ui-button--primary and .ui-button--secondary. Reference the existing class
names (.ui-button--primary, .ui-button--secondary) and their :hover/:active
blocks when adding these new :disabled and :focus-visible rules so styles are
consistent and precedence prevents hover/active from applying when disabled.

In `@components/ui/buttons/ButtonIcon/ButtonIcon.styles.css`:
- Around line 23-45: The .ui-button-icon--primary and .ui-button-icon--secondary
blocks duplicate identical declarations (font-semibold, :hover opacity-80,
:active opacity-70); refactor by extracting the common styles into a shared
selector (e.g., .ui-button-icon--primary, .ui-button-icon--secondary { ... })
and likewise combine the hover and active state rules into shared selectors
(.ui-button-icon--primary:hover, .ui-button-icon--secondary:hover { ... } and
.ui-button-icon--primary:active, .ui-button-icon--secondary:active { ... }) so
the three duplicated rule sets are consolidated while preserving the same
Tailwind `@apply` utilities.

In `@components/ui/inputs/DatePickerInput/__tests__/DatePickerInput.test.tsx`:
- Around line 67-84: Add a test to verify the popover closes after selecting a
date: render the same compound composition (DatePickerInput.Root with
DatePickerInput.Trigger and DatePickerInput.Popover), open the popover by
interacting with DatePickerInput.Trigger (e.g., userEvent.click on the trigger),
simulate selecting a date inside DatePickerInput.Popover (click the date element
rendered by the calendar), and then assert the popover is no longer in the
document or not visible (e.g., expect(queryByRole('dialog' or appropriate
role)).toBeNull()/not.toBeInTheDocument()). Use the existing helpers like
screen/getByRole and userEvent to perform interactions and reference
DatePickerInput.Trigger and DatePickerInput.Popover in the test.

In `@components/ui/inputs/DatePickerInput/DatePickerInput.styles.css`:
- Around line 82-84: The error text class .ui-date-picker-input__error uses the
default Tailwind color text-red-600 while the component uses the custom token
border-red-alert elsewhere; update .ui-date-picker-input__error to use the same
custom token (e.g., replace text-red-600 with text-red-alert or the project’s
text-red-alert utility) so error text and border colors are consistent across
the DatePickerInput styles.

In `@components/ui/inputs/TextInput/TextInput.styles.css`:
- Around line 21-27: The error styles are inconsistent: .ui-text-input--invalid
uses border-red-alert while .ui-text-input__error uses text-red-600; pick one
token and make both use it for consistent error styling. Update either
.ui-text-input--invalid to use border-red-600 to match text-red-600, or change
.ui-text-input__error to text-red-alert so both classes (.ui-text-input--invalid
and .ui-text-input__error) use the same color token across border and text.

In `@components/ui/Table/__tests__/Table.test.tsx`:
- Around line 51-56: The test currently casts firstRow with `as Element`, which
can hide null issues; instead assert the row exists using a null-safe assertion
(e.g., `expect(firstRow).not.toBeNull()` or `expect(firstRow).toBeTruthy()`)
right after `const firstRow = firstRowCell.closest('tr')`, then call
`fireEvent.click(firstRow)` and `fireEvent.keyDown(firstRow, { key: 'Enter' })`
using the asserted `firstRow` (referencing `firstRowCell.closest('tr')`,
`firstRow`, and the `fireEvent.click` / `fireEvent.keyDown` calls) so the test
fails clearly if the row is missing rather than silently casting a null.

In `@components/ui/Table/components/Body.tsx`:
- Around line 42-56: The clickable table row rendered in Body.tsx becomes
interactive when onRowClick is present but lacks an accessibility role; update
the <tr> element (where rowClassName, rowKey and onRowClick are used) to include
role="button" when onRowClick is provided, add an appropriate aria-* state such
as aria-pressed={false} or aria-label if needed, and ensure tabIndex remains set
to 0 and the existing onKeyDown handler is kept so screen readers and keyboard
users recognize the row as actionable.

In `@lib/orders/create-order.schema.ts`:
- Around line 10-17: The `side` schema currently mixes .enum(), .optional(), and
.refine(), causing contradictory validation: .optional() allows undefined but
.refine() rejects it. Fix by making `side` required: remove .optional() and also
remove the redundant .refine() (z.enum(['Compra','Venda'], { error: 'Lado e
obrigatorio' }) is sufficient) so validation is consistent; if instead you
intend `side` to be optional, keep .optional() and change the .refine()
predicate to allow undefined (e.g., val === undefined || val === 'Compra' || val
=== 'Venda').

In `@styles/theme.css`:
- Line 33: The CSS variable --text-size-16 duplicates the clamp expression used
elsewhere; change it to reference the canonical token --font-size-base instead
(i.e., set --text-size-16 to var(--font-size-base)) so the value is aliased to a
single source of truth and future drift is prevented; update any usages relying
on --text-size-16 accordingly to inherit the centralized value.

Comment thread app/api/orders/route.ts Outdated
Comment thread components/CreateOrderModal/__tests__/CreateOrderModal.test.tsx Outdated
Comment thread components/CreateOrderModal/index.tsx Outdated
Comment on lines +80 to +91
const response = await fetch('/api/orders', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(parsed.data),
});

if (!response.ok) {
setError('root', {
message: 'Nao foi possivel criar a ordem. Tente novamente.',
});
return;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Handle thrown network errors from fetch to avoid silent submit failures.
At Line 80, fetch can reject (offline, timeout, DNS), and the current flow only handles non-OK HTTP responses.

Suggested fix
-		const response = await fetch('/api/orders', {
-			method: 'POST',
-			headers: { 'Content-Type': 'application/json' },
-			body: JSON.stringify(parsed.data),
-		});
-
-		if (!response.ok) {
-			setError('root', {
-				message: 'Nao foi possivel criar a ordem. Tente novamente.',
-			});
-			return;
-		}
+		try {
+			const response = await fetch('/api/orders', {
+				method: 'POST',
+				headers: { 'Content-Type': 'application/json' },
+				body: JSON.stringify(parsed.data),
+			});
+
+			if (!response.ok) {
+				setError('root', {
+					message: 'Nao foi possivel criar a ordem. Tente novamente.',
+				});
+				return;
+			}
+		} catch {
+			setError('root', {
+				message: 'Nao foi possivel criar a ordem. Tente novamente.',
+			});
+			return;
+		}
📝 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 response = await fetch('/api/orders', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(parsed.data),
});
if (!response.ok) {
setError('root', {
message: 'Nao foi possivel criar a ordem. Tente novamente.',
});
return;
}
try {
const response = await fetch('/api/orders', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(parsed.data),
});
if (!response.ok) {
setError('root', {
message: 'Nao foi possivel criar a ordem. Tente novamente.',
});
return;
}
} catch {
setError('root', {
message: 'Nao foi possivel criar a ordem. Tente novamente.',
});
return;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/CreateOrderModal/index.tsx` around lines 80 - 91, The fetch call
that posts to '/api/orders' can throw network errors which are not currently
caught; wrap the await fetch(...) in a try/catch (around the code in
CreateOrderModal/index.tsx where you call fetch) and in the catch block call
setError('root', { message: 'Nao foi possivel criar a ordem. Verifique sua
conexao e tente novamente.' }) (or include the caught error.message for more
detail), then return to prevent further processing; keep the existing
response.ok handling inside the try.

Comment thread components/OrderGrid-bkp/order-grid-with-pagination.presenter.ts Outdated
Comment thread components/OrderGrid-bkp/useOrderGridWithPaginationController.ts Outdated
Comment on lines +21 to +24
- `app/` must contain **only** route files:
- `page.tsx`, `layout.tsx`, `loading.tsx`, `error.tsx`, `not-found.tsx`
- `route.ts` (handlers)
- Avoid reusable UI logic inside `app/`

Copy link
Copy Markdown

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 Next.js App Router, can non-route files (e.g., colocated components/helpers) exist inside the app/ directory, and what does official guidance recommend?

💡 Result:

Yes, non-route files such as colocated components and helpers can exist inside the Next.js App Router's app/ directory. Only specific special files (page.js/tsx, layout.js/tsx, loading.js/tsx, error.js/tsx, route.js/tsx) define routes; other files are safely colocated without becoming publicly accessible routes. Official Next.js documentation explicitly states: "project files can be safely colocated inside route segments in the app directory without accidentally being routable" and provides examples like app/blog/_components/Post.tsx for UI utilities. Recommendations include: - Colocate freely by default, as non-special files do not create routes. - Use private folders prefixed with _ (e.g., _components) to explicitly opt out of routing and organize non-routable files. - Route groups with (e.g., (marketing)) for organization without URL impact. - Alternatives: store files outside app/ (e.g., src/components), in top-level app/ folders, or in src/ for separation. Next.js is unopinionated about organization but supports colocation natively in app/.

Citations:


Align app/ structure guidance with Next.js colocation best practices.

The current "must contain only route files" rule contradicts official Next.js documentation. Per Next.js App Router guidance, non-route files safely colocate in app/ without becoming publicly accessible routes. Colocate route-segment utilities using private folders (e.g., app/blog/_components/Post.tsx) to keep related code organized. Reword to: "Route files (page.tsx, layout.tsx, etc.) define routes; colocate non-routable utilities using private folders (e.g., _components/), or store shared UI logic outside app/ to avoid app-router dependencies."

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

In `@docs/patterns/components.md` around lines 21 - 24, Update the guidance in
docs/patterns/components.md to align with Next.js colocation best practices:
remove the "must contain **only** route files" wording and instead state that
route files (page.tsx, layout.tsx, loading.tsx, error.tsx, not-found.tsx,
route.ts) define routes while non‑routable utilities may be colocated in app/
using private folders (example: app/blog/_components/Post.tsx) or kept outside
app/ for shared UI logic to avoid app-router coupling; mention using
leading-underscore folders (e.g., _components/) for private utilities to prevent
them from becoming routes.

Comment on lines +67 to +73
### Rule: no Tailwind utility strings inline in JSX

- **Do not** write `className="flex gap-2 ..."`.
- Components must use a **component-scoped CSS Style** (`<Component>.styles.css`).
- Tailwind utilities must be composed inside the CSS Style using `@apply`.
- Semantic class names in JSX are allowed and expected (for example: `className="create-order-modal__field"`).
- The restriction applies only to raw Tailwind utility strings inline in JSX.

Copy link
Copy Markdown

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:

What do Tailwind CSS v4 docs and community best practices recommend regarding inline utility classes in JSX vs extracting to CSS with @apply?

💡 Result:

Tailwind CSS v4 docs and community best practices strongly recommend using inline utility classes in JSX over extracting to CSS with @apply. The core philosophy is utility-first CSS: compose styles directly in HTML/JSX using utility classes like "flex p-4 bg-blue-500". This keeps all styling co-located, leverages Tailwind's purging for minimal CSS, enables variants (hover:, dark:, responsive), and avoids custom CSS maintenance. @apply is supported in v4 docs as an escape hatch for specific cases: overriding third-party library styles or when you must write custom CSS but want Tailwind design tokens. Example from docs: .select2-dropdown { @apply rounded-b-lg shadow-md; }. It requires @reference in scoped styles (Vue/Svelte/CSS modules) and can cause issues like unexpected purging or context problems in v4. For JSX/React, no official guidance promotes @apply; instead, docs emphasize complete static class names (use clsx/twMerge for conditionals), components for reuse, and avoiding dynamic concatenation. Adam Wathan (creator) advises against @apply, favoring theme vars or utilities for simplicity/performance. Community (shadcn/ui, guides) echoes: extract React components, not CSS classes.

Citations:


Update the "no inline Tailwind utilities in JSX" rule to align with official Tailwind v4 best practices.

The current rule contradicts Tailwind CSS v4 official documentation and community best practices. Tailwind's utility-first philosophy recommends composing styles directly in JSX using inline utility classes (e.g., className="flex gap-2 ..."), not extracting them to CSS with @apply. According to Tailwind docs, @apply is an escape hatch for third-party library overrides or custom CSS requirements—not the recommended pattern for React components. Community practices (shadcn/ui, community guides) consistently favor extracting React components for reuse rather than extracting CSS classes.

Revise the rule to recommend inline utilities for component styling, with CSS modules reserved for overriding third-party styles or truly custom CSS needs.

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

In `@docs/patterns/components.md` around lines 67 - 73, Update the "Rule: no
Tailwind utility strings inline in JSX" guidance to align with Tailwind v4 best
practices: change the rule text in the "no Tailwind utility strings inline in
JSX" section to recommend using inline Tailwind utility classes in JSX (e.g.,
className="flex gap-2 ...") as the default pattern for component styling, and
state that component-scoped CSS (.styles.css and `@apply`) should be reserved for
third-party overrides or truly custom CSS needs; update examples and the bullet
list to show inline utilities as preferred, keep semantic className usage as an
option for larger structural hooks, and explicitly document the exception
criteria where `@apply` or CSS modules are still appropriate.

Comment thread package.json Outdated
Comment thread server/db.json Outdated
Comment thread styles/globals.css
Comment on lines +4 to +5
@import url('https://db.onlinewebfonts.com/c/6c7eafed0f5bba0ad722f5373ecd4a54?family=Wix+Madefor+Text+Regular');

Copy link
Copy Markdown

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:

For Next.js 16, what are the recommended practices for loading custom fonts (next/font/local vs CSS @import from external domains), including privacy and performance trade-offs?

💡 Result:

For Next.js 16, the recommended practice for loading custom fonts is to use next/font/local, which self-hosts the fonts as static assets for optimal performance and privacy. Avoid CSS @import from external domains. How to use next/font/local: Import from 'next/font/local' and configure with src pointing to local font files (e.g., WOFF2 in app/fonts/ or public/): import localFont from 'next/font/local' const myFont = localFont({ src: './fonts/my-font.woff2', display: 'swap', // or 'block', etc. variable: '--font-myfont', // optional for CSS vars }) Apply via className on or body in root layout: For multiple weights/styles: src: [ { path: './fonts/Regular.woff2', weight: '400' }, { path: './fonts/Bold.woff2', weight: '700' }, ] Use variable fonts where possible for best flexibility. Privacy and performance trade-offs: next/font/local (recommended): - Performance: Fonts downloaded at build time, served from same domain as static assets. Automatic subsetting, preloading, size-adjust for zero layout shift (CLS). Reduces LCP/FCP. - Privacy: No external requests; fully self-hosted. GDPR-compliant, no third-party tracking. CSS @import from external domains (e.g., Google Fonts): - Performance: External network requests cause delays, potential FOIT/FOUT/CLS. No automatic optimization/subsetting/preloading. Slower render. - Privacy: Sends requests to external domains (e.g., fonts.googleapis.com), leaking user IP/visits. Privacy concerns, potential GDPR issues. Official docs emphasize next/font removes external requests for improved privacy/performance. Self-host even Google Fonts via next/font/google similarly. Manual @font-face in CSS possible but lacks next/font optimizations; still prefer next/font/local for customs.

Citations:


Replace external font @import with next/font/local.

The runtime @import from db.onlinewebfonts.com is render-blocking, leaks user data to third parties, and prevents automatic font optimization. Use next/font/local to self-host fonts with automatic subsetting, preloading, and CLS prevention—improving performance and privacy.

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

In `@styles/globals.css` around lines 4 - 5, Remove the external `@import` line from
globals.css and instead self-host the font using next/font/local: add the font
files under public/fonts (e.g., WixMadeforText-Regular.woff2), create a
localFont import in your Next.js root layout or _app (using next/font/local with
weight/style and preload), export its className or CSS variable, and apply that
className/variable to the html/body element (and delete the original `@import` in
styles/globals.css); ensure you configure fallback fonts and any required
subsets in the localFont call to retain optimization and CLS prevention.

Comment thread app/api/orders/route.ts Fixed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
app/api/orders/route.ts (1)

43-55: Consider wrapping the upstream call in a try-catch for explicit error handling.

If fetchWithTimeout throws (e.g., network failure, timeout), the error will propagate as a generic 500 without a structured response. While acceptable, explicit handling would provide clearer error messages to clients.

♻️ Optional improvement
+	let response;
+	try {
+		response = await fetchWithTimeout(`${env.apiBaseUrl}/orders`, {
+			method: 'POST',
+			headers: { 'Content-Type': 'application/json' },
+			body: JSON.stringify(payload),
+			cache: 'no-store',
+		});
+	} catch (error) {
+		return NextResponse.json(
+			{ message: 'Falha ao comunicar com o servidor de ordens.' },
+			{ status: 503 },
+		);
+	}
-	const response = await fetchWithTimeout(`${env.apiBaseUrl}/orders`, {
-		method: 'POST',
-		headers: { 'Content-Type': 'application/json' },
-		body: JSON.stringify(payload),
-		cache: 'no-store',
-	});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/orders/route.ts` around lines 43 - 55, The upstream POST using
fetchWithTimeout to `${env.apiBaseUrl}/orders` is not wrapped in try-catch, so
thrown errors (network/timeouts) leak as generic 500s; add a try-catch around
the call and subsequent response.ok check in the handler that calls
fetchWithTimeout, catch any Error, log or capture it, and return a structured
NextResponse.json with a clear message and an appropriate status (e.g., 502 or
504) including error.message; update the block that constructs the request
(references: fetchWithTimeout, env.apiBaseUrl, payload, NextResponse.json) to
ensure both non-ok responses and thrown exceptions produce consistent JSON
responses.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/api/orders/route.ts`:
- Line 26: Replace the insecure Math.random()-based uuidv4() usage with the
runtime-provided crypto.randomUUID(): update the code in route.ts where uuidv4()
is called (symbol: uuidv4) to call crypto.randomUUID() instead, remove or stop
importing the custom uuid helper from "@/lib/shared/uuid", and ensure any
callers expecting the same string ID type continue to work; if targeting
environments where crypto.randomUUID may be unavailable, add a small runtime
fallback that throws or polyfills explicitly rather than using Math.random().

---

Nitpick comments:
In `@app/api/orders/route.ts`:
- Around line 43-55: The upstream POST using fetchWithTimeout to
`${env.apiBaseUrl}/orders` is not wrapped in try-catch, so thrown errors
(network/timeouts) leak as generic 500s; add a try-catch around the call and
subsequent response.ok check in the handler that calls fetchWithTimeout, catch
any Error, log or capture it, and return a structured NextResponse.json with a
clear message and an appropriate status (e.g., 502 or 504) including
error.message; update the block that constructs the request (references:
fetchWithTimeout, env.apiBaseUrl, payload, NextResponse.json) to ensure both
non-ok responses and thrown exceptions produce consistent JSON responses.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 887c8d98-b494-446b-80ac-93e1f336e374

📥 Commits

Reviewing files that changed from the base of the PR and between 3d5722e and b1c0fcb.

📒 Files selected for processing (3)
  • .vscode/settings.json
  • app/api/orders/route.ts
  • lib/shared/uuid.ts
✅ Files skipped from review due to trivial changes (2)
  • .vscode/settings.json
  • lib/shared/uuid.ts

Comment thread app/api/orders/route.ts Outdated
- Deleted FilterSelect, FilterTextInput, FiltersActions, FiltersBar, FiltersFields, Head, Pagination, Root, Table, Toolbar components.
- Removed associated tests for the deleted components.
- Cleaned up filters configuration and filter controller logic.
- Updated package.json to ensure consistent dependencies.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (5)
app/api/orders/route.ts (1)

12-13: Consider returning a clearer error for malformed JSON.

When request.json() fails, body becomes null, and safeParse will produce generic validation errors. For better API ergonomics, detect this case explicitly.

💡 Suggested improvement
-	const body = await request.json().catch(() => null);
-	const parsedBody = createOrderSchema.safeParse(body);
+	const body = await request.json().catch(() => null);
+	if (body === null) {
+		return NextResponse.json(
+			{ message: 'Corpo da requisição inválido ou ausente.' },
+			{ status: 400 },
+		);
+	}
+	const parsedBody = createOrderSchema.safeParse(body);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/orders/route.ts` around lines 12 - 13, The handler currently sets
body = await request.json().catch(() => null) and then calls
createOrderSchema.safeParse(body), which yields generic validation errors when
JSON parsing failed; update this to detect the parse failure explicitly (e.g.,
catch the JSON parse error from request.json() and return or throw a 400
response with a clear "Malformed JSON" message) before calling
createOrderSchema.safeParse so that malformed JSON returns a distinct,
user-friendly error instead of generic schema errors.
components/CreateOrderModal/index.tsx (1)

38-38: Minor: Unusual indentation before return statement.

The return statement has extra leading whitespace. This appears to be a formatting inconsistency.

🧹 Fix indentation
-		return (
+	return (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/CreateOrderModal/index.tsx` at line 38, The return statement in
the CreateOrderModal component has extra leading whitespace causing inconsistent
indentation; open the component function (CreateOrderModal) and adjust the
indentation so the line with "return (" aligns with the surrounding block (same
indentation level as the opening of the function or preceding JSX/logic),
ensuring consistent spacing for the entire render block.
server/db.json (2)

1113-1114: Consider cleaning up test artifacts in seed data.

Some entries have placeholder-like instrument values ("ddd", "ERE", "AAA", "WEA") that appear to be manual test artifacts. While harmless for development, consider either removing them or using realistic instrument symbols for consistency with the rest of the dataset.

Also applies to: 1132-1133, 1151-1152, 1170-1171, 1189-1190

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

In `@server/db.json` around lines 1113 - 1114, Replace or remove the test-artifact
instrument values ("ddd", "ERE", "AAA", "WEA") in the seed JSON entries that
currently set "instrument": "ddd"/"ERE"/"AAA"/"WEA" (the objects with keys
"instrument" and "side") and either delete those test records or replace them
with realistic instrument symbols consistent with the rest of the dataset;
ensure the modified JSON objects remain valid and retain their original
structure (same keys like "instrument" and "side") so seeding/parsing code
continues to work.

1203-1204: $schema conventionally appears at the top of JSON files.

While valid at any position, placing $schema at the top of the file is the common convention for better discoverability.

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

In `@server/db.json` around lines 1203 - 1204, Move the "$schema" property to the
top of server/db.json for better discoverability; locate the "$schema" entry
(currently near the end of the file) and cut/paste it so it becomes the first
property in the root JSON object, ensuring you preserve JSON syntax
(commas/whitespace) when relocating the "$schema" entry.
components/CreateOrderModal/useCreateOrderForm.ts (1)

63-91: Nested try-catch structure can be simplified.

The outer try-catch (lines 63, 86-91) only catches errors that occur after the inner try-catch completes. Since closeModal() and onCreated?.() are unlikely to throw, this catch block may never execute. Consider flattening the structure.

♻️ Suggested simplification
-		try {
-			let response;
-			try {
-				response = await fetch('/api/orders', {
-					method: 'POST',
-					headers: { 'Content-Type': 'application/json' },
-					body: JSON.stringify(parsed.data),
-				});
-			} catch (networkError) {
-				setError('root', {
-					message: 'Erro de rede. Verifique sua conexão e tente novamente.',
-				});
-				onError?.(networkError);
-				return;
-			}
-			if (!response.ok) {
-				setError('root', {
-					message: 'Não foi possível criar a ordem. Tente novamente.',
-				});
-				return;
-			}
-			closeModal();
-			onCreated?.();
-		} catch (err) {
-			setError('root', {
-				message: 'Erro inesperado. Tente novamente.',
-			});
-			onError?.(err);
-		}
+		let response;
+		try {
+			response = await fetch('/api/orders', {
+				method: 'POST',
+				headers: { 'Content-Type': 'application/json' },
+				body: JSON.stringify(parsed.data),
+			});
+		} catch (networkError) {
+			setError('root', {
+				message: 'Erro de rede. Verifique sua conexão e tente novamente.',
+			});
+			onError?.(networkError);
+			return;
+		}
+		if (!response.ok) {
+			setError('root', {
+				message: 'Não foi possível criar a ordem. Tente novamente.',
+			});
+			return;
+		}
+		closeModal();
+		onCreated?.();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/CreateOrderModal/useCreateOrderForm.ts` around lines 63 - 91, The
current nested try/catch is unnecessary—remove the inner try and use a single
try around the await fetch and subsequent logic (response.ok check,
closeModal(), onCreated?.()). On fetch failure the catch should setError('root',
{ message: 'Erro de rede. Verifique sua conexão e tente novamente.' }) and call
onError?.(err); if fetch succeeds but response.ok is false, call
setError('root', { message: 'Não foi possível criar a ordem. Tente novamente.'
}) and return without calling onError; otherwise proceed to closeModal() and
onCreated?.(). Keep references to fetch('/api/orders'), setError('root', …),
closeModal(), onCreated?.(), and onError?.() so you update those exact call
sites.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/api/orders/route.ts`:
- Around line 43-48: The call to fetchWithTimeout (the code assigning to
response) can throw on timeout or network errors; wrap that fetchWithTimeout
invocation in a try-catch around the POST to `${env.apiBaseUrl}/orders` (the
block that creates `response` from `payload`) and handle errors by returning a
clear HTTP Response instead of letting exceptions bubble: catch
AbortError/timeouts separately if possible (or check error.name ===
'AbortError') and return a 504 with a JSON/plain message indicating an upstream
timeout, and for other network errors return a 502 with a JSON/plain message
indicating upstream/network failure; ensure you still handle non-2xx responses
from `response` after the try and propagate them appropriately.

---

Nitpick comments:
In `@app/api/orders/route.ts`:
- Around line 12-13: The handler currently sets body = await
request.json().catch(() => null) and then calls
createOrderSchema.safeParse(body), which yields generic validation errors when
JSON parsing failed; update this to detect the parse failure explicitly (e.g.,
catch the JSON parse error from request.json() and return or throw a 400
response with a clear "Malformed JSON" message) before calling
createOrderSchema.safeParse so that malformed JSON returns a distinct,
user-friendly error instead of generic schema errors.

In `@components/CreateOrderModal/index.tsx`:
- Line 38: The return statement in the CreateOrderModal component has extra
leading whitespace causing inconsistent indentation; open the component function
(CreateOrderModal) and adjust the indentation so the line with "return (" aligns
with the surrounding block (same indentation level as the opening of the
function or preceding JSX/logic), ensuring consistent spacing for the entire
render block.

In `@components/CreateOrderModal/useCreateOrderForm.ts`:
- Around line 63-91: The current nested try/catch is unnecessary—remove the
inner try and use a single try around the await fetch and subsequent logic
(response.ok check, closeModal(), onCreated?.()). On fetch failure the catch
should setError('root', { message: 'Erro de rede. Verifique sua conexão e tente
novamente.' }) and call onError?.(err); if fetch succeeds but response.ok is
false, call setError('root', { message: 'Não foi possível criar a ordem. Tente
novamente.' }) and return without calling onError; otherwise proceed to
closeModal() and onCreated?.(). Keep references to fetch('/api/orders'),
setError('root', …), closeModal(), onCreated?.(), and onError?.() so you update
those exact call sites.

In `@server/db.json`:
- Around line 1113-1114: Replace or remove the test-artifact instrument values
("ddd", "ERE", "AAA", "WEA") in the seed JSON entries that currently set
"instrument": "ddd"/"ERE"/"AAA"/"WEA" (the objects with keys "instrument" and
"side") and either delete those test records or replace them with realistic
instrument symbols consistent with the rest of the dataset; ensure the modified
JSON objects remain valid and retain their original structure (same keys like
"instrument" and "side") so seeding/parsing code continues to work.
- Around line 1203-1204: Move the "$schema" property to the top of
server/db.json for better discoverability; locate the "$schema" entry (currently
near the end of the file) and cut/paste it so it becomes the first property in
the root JSON object, ensuring you preserve JSON syntax (commas/whitespace) when
relocating the "$schema" entry.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ded01310-a0df-4a53-9429-4aadce40eb9d

📥 Commits

Reviewing files that changed from the base of the PR and between b1c0fcb and 88f5ddc.

📒 Files selected for processing (10)
  • .gitignore
  • app/api/orders/route.ts
  • components/CreateOrderModal/__tests__/CreateOrderModal.test.tsx
  • components/CreateOrderModal/index.tsx
  • components/CreateOrderModal/useCreateOrderForm.ts
  • components/OrderGrid/components/Filter/__tests__/FilterSelect.test.tsx
  • lib/orders/create-order.schema.ts
  • lib/shared/uuid.ts
  • package.json
  • server/db.json
✅ Files skipped from review due to trivial changes (4)
  • .gitignore
  • package.json
  • lib/orders/create-order.schema.ts
  • components/CreateOrderModal/tests/CreateOrderModal.test.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • components/OrderGrid/components/Filter/tests/FilterSelect.test.tsx
  • lib/shared/uuid.ts

Comment thread app/api/orders/route.ts
Comment on lines +43 to +48
const response = await fetchWithTimeout(`${env.apiBaseUrl}/orders`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
cache: 'no-store',
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Wrap upstream fetch in try-catch to handle network/timeout errors.

fetchWithTimeout can throw (timeout abort, DNS failure, network offline). Currently, these exceptions propagate uncaught, resulting in an opaque 500 response. Handle the exception to return a meaningful error.

🛡️ Proposed fix
-	const response = await fetchWithTimeout(`${env.apiBaseUrl}/orders`, {
-		method: 'POST',
-		headers: { 'Content-Type': 'application/json' },
-		body: JSON.stringify(payload),
-		cache: 'no-store',
-	});
+	let response: Response;
+	try {
+		response = await fetchWithTimeout(`${env.apiBaseUrl}/orders`, {
+			method: 'POST',
+			headers: { 'Content-Type': 'application/json' },
+			body: JSON.stringify(payload),
+			cache: 'no-store',
+		});
+	} catch {
+		return NextResponse.json(
+			{ message: 'Falha ao conectar com o servidor. Tente novamente.' },
+			{ status: 503 },
+		);
+	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/api/orders/route.ts` around lines 43 - 48, The call to fetchWithTimeout
(the code assigning to response) can throw on timeout or network errors; wrap
that fetchWithTimeout invocation in a try-catch around the POST to
`${env.apiBaseUrl}/orders` (the block that creates `response` from `payload`)
and handle errors by returning a clear HTTP Response instead of letting
exceptions bubble: catch AbortError/timeouts separately if possible (or check
error.name === 'AbortError') and return a 504 with a JSON/plain message
indicating an upstream timeout, and for other network errors return a 502 with a
JSON/plain message indicating upstream/network failure; ensure you still handle
non-2xx responses from `response` after the try and propagate them
appropriately.

@atelesjr
atelesjr merged commit a032de1 into main Mar 27, 2026
3 checks passed
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.

2 participants