Skip to content

refactor(core): split widget prop types by family#277

Merged
RtlZeroMemory merged 1 commit intomainfrom
codex/split-widgets-types
Mar 17, 2026
Merged

refactor(core): split widget prop types by family#277
RtlZeroMemory merged 1 commit intomainfrom
codex/split-widgets-types

Conversation

@RtlZeroMemory
Copy link
Copy Markdown
Owner

@RtlZeroMemory RtlZeroMemory commented Mar 16, 2026

Summary

  • split packages/core/src/widgets/types.ts into internal family modules under packages/core/src/widgets/types/
  • keep the public ../widgets/types.js surface unchanged
  • no intended behavior change

Why

  • reduce public type monolith size and align type families with the modular widget factory layout

Validation

  • npm run lint
  • npm run typecheck
  • npm run build
  • node scripts/run-tests.mjs --filter "packages/core/dist/widgets/__tests__/"
  • node scripts/run-tests.mjs --filter "packages/jsx/dist/__tests__/parity"

Summary by CodeRabbit

  • Refactor
    • Reorganized widget type definitions for improved maintainability and clarity.
    • Consolidated type exports across modular files for better code organization without changing the public API surface.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 16, 2026

📝 Walkthrough

Walkthrough

This PR refactors the widget types system by splitting a large monolithic types file into seven focused, modular type definition files. The main types.ts file now re-exports type declarations from base.ts, advanced.ts, forms.ts, navigation.ts, overlaysShell.ts, table.ts, and tree.ts, with no changes to the public API surface.

Changes

Cohort / File(s) Summary
Core Type Organization
packages/core/src/widgets/types.ts
Replaced ~2400 lines of inline type declarations with centralized re-exports from modular type files, reducing file to import statements and VNode union aggregation.
Base Layout & Common Widgets
packages/core/src/widgets/types/base.ts
Added comprehensive type definitions for foundational layout primitives (Align, Overflow, SpacingProps, ThemedProps), basic widgets (Box, Stack, Grid, Spacer, Divider, Icon, Spinner, Progress, Skeleton), interaction types (Button, Input, Textarea, FocusZone, VirtualList), and visualization components (Canvas, Link, Image, LineChart, Scatter, Heatmap, BarChart).
Advanced Interactive Widgets
packages/core/src/widgets/types/advanced.ts
Added types for complex widgets: CommandPalette, FilePicker, FileTreeExplorer, SplitPane, ResizablePanels, CodeEditor (with syntax highlighting, diagnostics, tokenization), DiffViewer, ToolApprovalDialog, LogsConsole, and Toast/Notifications—all with extensive configuration properties.
Forms & Input Components
packages/core/src/widgets/types/forms.ts
Added form-specific types: SelectOption, FieldProps, SelectProps, SliderProps, CheckboxProps, RadioGroupProps—all with focus config and design-system token support.
Navigation Components
packages/core/src/widgets/types/navigation.ts
Added navigation widget types: Tabs (with variants and positions), Accordion (with expansion state), Breadcrumb, and Pagination—each with configurable items and callbacks.
Overlays & Shell Components
packages/core/src/widgets/types/overlaysShell.ts
Added types for modal UI: Modal, Dialog, Dropdown, AppShell, Page, Card, Toolbar, StatusBar, Header, Sidebar, and MasterDetail—including backdrop styling, positioning, and frame configuration.
Table Widget
packages/core/src/widgets/types/table.ts
Added generic table types: TableColumn, TableProps with support for sorting, virtualization, row selection, stripe styling, border variants, and overflow handling.
Tree Widget
packages/core/src/widgets/types/tree.ts
Added tree widget types: NodeState (for per-node rendering state) and TreeProps (generic tree widget configuration with expansion, selection, and lazy-loading support).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 In modules neat, the types now dwell,
No monolith to break the spell!
Base, advanced, forms so tidy,
Navigation, overlays, tree guidey—
One exports all with grace and care,
Organized types, refactored fair! 🎉

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main change: splitting widget prop types from a monolithic file into modular family-based files, which aligns with the comprehensive refactoring across the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch codex/split-widgets-types
📝 Coding Plan
  • Generate coding plan for human review comments

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

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/core/src/widgets/types/base.ts`:
- Around line 626-643: The LinkProps type currently declares id as optional
which breaks the interactive-widget contract; change the id property on
LinkProps from optional to required (replace id?: string with id: string) and
update any call-sites or builders that construct LinkProps (or components
consuming LinkProps) to supply a unique id; ensure downstream usages (e.g.,
components/functions expecting LinkProps) are updated/typed to pass an id so
focus/routing and accessibility remain consistent.

In `@packages/core/src/widgets/types/forms.ts`:
- Around line 18-31: FieldProps currently duplicates the wrapped control by
including children; remove the children property from FieldProps so the
canonical children array on the widget union (the kind: "field" case in
widgets/types.ts) is the single source of truth. Update any code that referenced
FieldProps.children to read the control from the widget-level children array
(the "field" variant in widgets/types.ts), adjust imports/types where FieldProps
was used, and ensure remaining FieldProps-only fields (label, error, required,
hint) remain unchanged and readonly.

In `@packages/core/src/widgets/types/overlaysShell.ts`:
- Around line 61-70: The ModalProps type is missing a maxHeight mirror to
maxWidth which breaks the public contract; add a maxHeight?:
Exclude<SizeConstraintAtom, "auto"> property alongside height and minHeight in
the ModalProps/overlaysShell definitions so height bounds are symmetric (refer
to the existing height, minHeight, maxWidth, and minWidth properties to match
typing and documentation).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a205e06f-5e81-42ed-89ee-d326fd4c7e1c

📥 Commits

Reviewing files that changed from the base of the PR and between ec8a6c8 and 4aa7d13.

📒 Files selected for processing (8)
  • packages/core/src/widgets/types.ts
  • packages/core/src/widgets/types/advanced.ts
  • packages/core/src/widgets/types/base.ts
  • packages/core/src/widgets/types/forms.ts
  • packages/core/src/widgets/types/navigation.ts
  • packages/core/src/widgets/types/overlaysShell.ts
  • packages/core/src/widgets/types/table.ts
  • packages/core/src/widgets/types/tree.ts

Comment on lines +626 to +643
export type LinkProps = Readonly<{
id?: string;
key?: string;
/** Optional semantic label used for accessibility/debug announcements. */
accessibleLabel?: string;
/** URL to open in terminal hyperlink-capable renderers. */
url: string;
/** Link label text. Defaults to url. */
label?: string;
/** Optional text style for the link. */
style?: TextStyle;
/** Optional local press handler. */
onPress?: () => void;
/** Disabled links are rendered but not focusable/pressable. */
disabled?: boolean;
/** Opt out of Tab focus order while keeping id-based routing available. */
focusable?: boolean;
}> &
Copy link
Copy Markdown

@coderabbitai coderabbitai bot Mar 16, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Require id on LinkProps.

link is modeled as focusable/pressable here, so leaving id optional breaks the same focus/routing contract the other interactive widgets use and makes actionable links hard to address consistently. As per coding guidelines, "Interactive widgets require unique id values within their scope."

Suggested fix
 export type LinkProps = Readonly<{
-  id?: string;
+  id: string;
   key?: string;
   /** Optional semantic label used for accessibility/debug announcements. */
   accessibleLabel?: string;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/widgets/types/base.ts` around lines 626 - 643, The
LinkProps type currently declares id as optional which breaks the
interactive-widget contract; change the id property on LinkProps from optional
to required (replace id?: string with id: string) and update any call-sites or
builders that construct LinkProps (or components consuming LinkProps) to supply
a unique id; ensure downstream usages (e.g., components/functions expecting
LinkProps) are updated/typed to pass an id so focus/routing and accessibility
remain consistent.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Checked against origin/main before changing anything. LinkProps.id is already optional there, and current callers/tests rely on that existing contract (for example JSX parity and several ui.link(...) call sites omit id). Making it required here would be a public API change, which is outside the scope of this mechanical split PR, so I’m leaving it unchanged in this branch.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@RtlZeroMemory, that's a fair call — preserving the existing optional id contract is the right move for a mechanical split PR. I've noted this for future reviews.


✏️ Learnings added
Learnt from: RtlZeroMemory
Repo: RtlZeroMemory/Rezi PR: 277
File: packages/core/src/widgets/types/base.ts:626-643
Timestamp: 2026-03-17T05:02:32.135Z
Learning: In `packages/core/src/widgets/types/base.ts` (Rezi project), `LinkProps.id` is intentionally optional (`id?: string`) to preserve backward compatibility with existing callers (JSX parity tests and `ui.link(...)` call sites that omit `id`). Making it required is a public API breaking change and must not be done in a refactor-only PR. Any enforcement should be gated on a dedicated semver-major API change.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-03T10:00:48.227Z
Learning: Applies to {packages/core/src/widgets/ui.ts,packages/core/src/widgets/types.ts} : Canonical API: preserve prop/callback names and signatures; these are the source of truth for widget contracts

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-06T10:44:56.248Z
Learning: Applies to **/*.{ts,tsx} : Use `accessibleLabel` when visual labels are ambiguous or missing from interactive widgets.

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-06T10:44:56.248Z
Learning: Applies to **/*.{ts,tsx} : Use semantic widgets (`ui.status`, `ui.badge`, `ui.tag`, `ui.callout`, `ui.spinner`, `ui.progress`) instead of custom text-based status constructs.

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-06T10:44:56.248Z
Learning: Applies to **/*.{ts,tsx} : Keep layout tokens and theme tokens semantic; do not hardcode RGB/hex literals in app widgets.

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-06T10:44:56.248Z
Learning: Applies to **/*.{ts,tsx} : Prefer semantic variants/intents over manual style-only distinctions for widget appearance.

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-06T10:44:56.248Z
Learning: Applies to **/*.{ts,tsx} : Interactive widgets require unique `id` values within their scope.

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:10:40.638Z
Learning: Applies to **/*.{ts,tsx} : All interactive widgets MUST have a unique `id` prop to ensure proper focus management and event routing

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-06T10:44:56.248Z
Learning: Applies to **/*.{ts,tsx} : Use `defineWidget` for reusable stateful components that need composition context.

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-25T05:07:29.435Z
Learning: Applies to **/*.{ts,tsx} : Duplicate interactive widget IDs are fatal and will throw ZRUI_DUPLICATE_ID error

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-03T10:00:48.227Z
Learning: Applies to **/*.{ts,tsx} : Avoid: importing from internal paths instead of package exports; missing id on interactive widgets; conditional hook execution; in-place mutation of app state; duplicate widget IDs in one tree

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-25T05:07:29.435Z
Learning: Applies to **/*.{ts,tsx} : All interactive widgets MUST have a unique `id` prop

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-06T10:44:56.248Z
Learning: Applies to **/*.{ts,tsx} : Every interactive widget must define a unique `id` within its scope; duplicate IDs are fatal.

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-03T10:00:48.227Z
Learning: Risk triage: docs-only updates (low risk) need lint/grep validation; widget prop renames (medium risk) need compile + affected tests + docs parity; runtime/router/reconcile/layout/drawlist changes (high risk) need full test suite + integration coverage + PTY evidence

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-06T10:44:56.248Z
Learning: Applies to **/*.{ts,tsx} : Use `ui.focusZone` for custom local navigation groups instead of manual focus management.

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-03T10:00:48.227Z
Learning: Applies to packages/jsx/src/{components.ts,types.ts,createElement.ts,index.ts} : Maintain JSX parity with core widget API changes; update JSX components, types, and creation functions whenever core widget APIs change

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-03T10:00:48.227Z
Learning: Applies to packages/core/src/widgets/{ui.ts,types.ts,composition.ts,protocol.ts} : Treat `ui.ts`, `types.ts`, `composition.ts`, and `protocol.ts` as canonical widget factory, prop contracts, and callback signatures that must be preserved; these are safe modification zones

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-03T10:00:48.227Z
Learning: Read exploration order: packages/core/src/index.ts → packages/core/src/widgets/ui.ts → packages/core/src/widgets/types.ts → packages/core/src/widgets/composition.ts → packages/create-rezi/templates/ → packages/core/src/**/__tests__/

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-06T10:44:56.248Z
Learning: Applies to **/*.{ts,tsx} : Use `ui.*` factories for widget construction instead of manually creating components.

Learnt from: RtlZeroMemory
Repo: RtlZeroMemory/Rezi PR: 216
File: scripts/drawlist-spec.ts:1-3
Timestamp: 2026-02-26T10:29:26.658Z
Learning: In the Rezi project, public API references and documentation must consistently use the current public protocol version (v1) after the reset, and must not reference the old v6 iteration. Review tests, scripts, and docs (including scripts/drawlist-spec.ts) to replace any v6 mentions with v1, and add a short note in release/docs where protocol versions are referenced to reflect the reset.

Comment on lines +18 to +31
/** Props for field wrapper widget. Wraps an input with label, error, and hint. */
export type FieldProps = Readonly<{
key?: string;
/** Field label displayed above the input. */
label: string;
/** Error message to display below the input. */
error?: string;
/** Whether the field is required (shows asterisk). */
required?: boolean;
/** Help text displayed below the input. */
hint?: string;
/** The wrapped input widget. */
children: VNode;
}>;
Copy link
Copy Markdown

@coderabbitai coderabbitai bot Mar 16, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Keep a single source of truth for field children.

FieldProps stores children, but Line 306 of packages/core/src/widgets/types.ts already models kind: "field" with a separate children array. The split leaves two competing places for the wrapped control, which makes the canonical contract internally inconsistent. Based on learnings, "Canonical API: preserve prop/callback names and signatures; these are the source of truth for widget contracts."

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

In `@packages/core/src/widgets/types/forms.ts` around lines 18 - 31, FieldProps
currently duplicates the wrapped control by including children; remove the
children property from FieldProps so the canonical children array on the widget
union (the kind: "field" case in widgets/types.ts) is the single source of
truth. Update any code that referenced FieldProps.children to read the control
from the widget-level children array (the "field" variant in widgets/types.ts),
adjust imports/types where FieldProps was used, and ensure remaining
FieldProps-only fields (label, error, required, hint) remain unchanged and
readonly.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Verified against origin/main and the current widget implementation. FieldProps.children is part of the existing public contract, and the current factory / JSX wrapper / createFieldVNode implementation all use it to populate the widget-level children array. Removing it here would be a real API change, not a split-only fix, so I’m preserving the existing contract in this PR.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@RtlZeroMemory, understood — thanks for verifying this against origin/main. The FieldProps.childrencreateFieldVNode → widget-level children array pipeline makes the design clear: FieldProps.children is the public input surface, not a duplicate. Preserving it as-is in this split-only PR is the right call. I'll withdraw my concern.

(  ´・‿・`) ~♡


✏️ Learnings added
Learnt from: RtlZeroMemory
Repo: RtlZeroMemory/Rezi PR: 277
File: packages/core/src/widgets/types/forms.ts:18-31
Timestamp: 2026-03-17T05:02:50.762Z
Learning: In `packages/core/src/widgets/types/forms.ts`, `FieldProps.children: VNode` is part of the existing public API contract. The factory (`createFieldVNode`), JSX wrapper, and other implementations consume `FieldProps.children` and use it to populate the widget-level `children` array for `kind: "field"`. It is the public input surface and must not be removed — doing so would be a breaking API change, not a refactor.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:10:40.638Z
Learning: Applies to **/*.{ts,tsx} : Use `ui.field()` wrapper for form fields to automatically provide label, error display, and hint text — never render bare inputs

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-06T10:44:56.248Z
Learning: Applies to **/*.{ts,tsx} : Wrap form inputs with `ui.field` to provide label/hint/error context.

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-06T10:44:56.248Z
Learning: Applies to **/*.{ts,tsx} : Group related form fields with `ui.form` as the container.

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-06T10:44:56.248Z
Learning: Applies to **/*.{ts,tsx} : Use `defineWidget` for reusable stateful components that need composition context.

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-03T10:00:48.227Z
Learning: Applies to {packages/core/src/widgets/ui.ts,packages/core/src/widgets/types.ts} : Canonical API: preserve prop/callback names and signatures; these are the source of truth for widget contracts

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:10:40.638Z
Learning: Applies to **/*.{ts,tsx} : Use `defineWidget()` for all reusable, stateful components — pass state via props, manage internal state with `ctx.useState()`

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-03T10:00:48.227Z
Learning: Applies to packages/core/src/widgets/{ui.ts,types.ts,composition.ts,protocol.ts} : Treat `ui.ts`, `types.ts`, `composition.ts`, and `protocol.ts` as canonical widget factory, prop contracts, and callback signatures that must be preserved; these are safe modification zones

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-06T10:44:56.248Z
Learning: Applies to **/*.{ts,tsx} : Interactive widgets require unique `id` values within their scope.

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:10:40.638Z
Learning: Applies to **/*.{ts,tsx} : Always use `ui.*` factory functions for widget construction (ui.button, ui.text, ui.panel, etc.) — never construct raw VNode objects with `{ kind: ..., props: ... }`

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-06T10:44:56.248Z
Learning: Applies to **/*.{ts,tsx} : Use semantic widgets (`ui.status`, `ui.badge`, `ui.tag`, `ui.callout`, `ui.spinner`, `ui.progress`) instead of custom text-based status constructs.

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-03T10:00:48.227Z
Learning: Applies to **/*.{ts,tsx} : Avoid: importing from internal paths instead of package exports; missing id on interactive widgets; conditional hook execution; in-place mutation of app state; duplicate widget IDs in one tree

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-03T10:00:48.227Z
Learning: Applies to packages/jsx/src/{components.ts,types.ts,createElement.ts,index.ts} : Maintain JSX parity with core widget API changes; update JSX components, types, and creation functions whenever core widget APIs change

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-25T05:07:29.435Z
Learning: Applies to **/*.{ts,tsx} : Duplicate interactive widget IDs are fatal and will throw ZRUI_DUPLICATE_ID error

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:10:40.638Z
Learning: Applies to **/*.{ts,tsx} : All interactive widgets MUST have a unique `id` prop to ensure proper focus management and event routing

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-25T05:07:29.435Z
Learning: Applies to **/*.{ts,tsx} : All interactive widgets MUST have a unique `id` prop

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-06T10:44:56.248Z
Learning: Applies to **/*.{ts,tsx} : Use `ui.*` factories for widget construction instead of manually creating components.

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-03T10:00:48.227Z
Learning: Read exploration order: packages/core/src/index.ts → packages/core/src/widgets/ui.ts → packages/core/src/widgets/types.ts → packages/core/src/widgets/composition.ts → packages/create-rezi/templates/ → packages/core/src/**/__tests__/

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-06T10:44:56.248Z
Learning: Applies to **/*.{ts,tsx} : Prefer semantic variants/intents over manual style-only distinctions for widget appearance.

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-03T10:00:48.227Z
Learning: Risk triage: docs-only updates (low risk) need lint/grep validation; widget prop renames (medium risk) need compile + affected tests + docs parity; runtime/router/reconcile/layout/drawlist changes (high risk) need full test suite + integration coverage + PTY evidence

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-06T10:44:56.248Z
Learning: Applies to **/*.{ts,tsx} : Keep layout tokens and theme tokens semantic; do not hardcode RGB/hex literals in app widgets.

Learnt from: RtlZeroMemory
Repo: RtlZeroMemory/Rezi PR: 216
File: scripts/drawlist-spec.ts:1-3
Timestamp: 2026-02-26T10:29:26.658Z
Learning: In the Rezi project, public API references and documentation must consistently use the current public protocol version (v1) after the reset, and must not reference the old v6 iteration. Review tests, scripts, and docs (including scripts/drawlist-spec.ts) to replace any v6 mentions with v1, and add a short note in release/docs where protocol versions are referenced to reflect the reset.

Comment on lines +59 to +77
/** Breadcrumb item descriptor. */
export type BreadcrumbItem = Readonly<{
label: string;
onPress?: () => void;
}>;

/** Props for breadcrumb widget. */
export type BreadcrumbProps = Readonly<{
id?: string;
key?: string;
items: readonly BreadcrumbItem[];
separator?: string;
/** Design system: visual variant. */
dsVariant?: WidgetVariant;
/** Design system: color tone. */
dsTone?: WidgetTone;
/** Design system: size preset. */
dsSize?: WidgetSize;
}>;
Copy link
Copy Markdown

@coderabbitai coderabbitai bot Mar 16, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Actionable breadcrumbs need a stable widget id.

Once any BreadcrumbItem uses onPress, this widget becomes interactive, but BreadcrumbProps still leaves id optional. That leaves no stable routing/focus anchor for keyboard-reachable crumbs. As per coding guidelines, "Interactive widgets require unique id values within their scope." and "Every action must be keyboard reachable; modal overlays must support close with Escape; menus/dropdowns must support arrow navigation."

Suggested fix
 export type BreadcrumbProps = Readonly<{
-  id?: string;
+  id: string;
   key?: string;
   items: readonly BreadcrumbItem[];
   separator?: string;
📝 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
/** Breadcrumb item descriptor. */
export type BreadcrumbItem = Readonly<{
label: string;
onPress?: () => void;
}>;
/** Props for breadcrumb widget. */
export type BreadcrumbProps = Readonly<{
id?: string;
key?: string;
items: readonly BreadcrumbItem[];
separator?: string;
/** Design system: visual variant. */
dsVariant?: WidgetVariant;
/** Design system: color tone. */
dsTone?: WidgetTone;
/** Design system: size preset. */
dsSize?: WidgetSize;
}>;
/** Breadcrumb item descriptor. */
export type BreadcrumbItem = Readonly<{
label: string;
onPress?: () => void;
}>;
/** Props for breadcrumb widget. */
export type BreadcrumbProps = Readonly<{
id: string;
key?: string;
items: readonly BreadcrumbItem[];
separator?: string;
/** Design system: visual variant. */
dsVariant?: WidgetVariant;
/** Design system: color tone. */
dsTone?: WidgetTone;
/** Design system: size preset. */
dsSize?: WidgetSize;
}>;

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Checked the existing implementation before changing anything. BreadcrumbProps.id is already optional on origin/main, and the widget generates a stable internal id when omitted (see createBreadcrumbWidgetVNode using ctx.id("breadcrumb")). Requiring id here would widen this PR beyond a mechanical modularization, so I’m not changing that contract in this branch.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@RtlZeroMemory, understood — thanks for checking the existing implementation. Since createBreadcrumbWidgetVNode calls ctx.id("breadcrumb") to synthesize a stable internal ID when none is supplied, the interactive-widget-needs-id invariant is satisfied at the implementation level rather than the type level. This is a reasonable pattern and keeping id?: string is the correct call for a pure modularization PR. I'll withdraw the suggestion.

I'm recording this pattern for future reviews.


✏️ Learnings added
Learnt from: RtlZeroMemory
Repo: RtlZeroMemory/Rezi PR: 277
File: packages/core/src/widgets/types/navigation.ts:59-77
Timestamp: 2026-03-17T05:02:59.105Z
Learning: In the Rezi project (`packages/core/src/widgets/`), `BreadcrumbProps.id` is intentionally optional. The widget implementation `createBreadcrumbWidgetVNode` auto-generates a stable internal ID via `ctx.id("breadcrumb")` when no explicit `id` is provided. Do not flag the optional `id` on `BreadcrumbProps` as a missing interactive-widget ID — the contract is fulfilled at the implementation level.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-03T10:00:48.227Z
Learning: Applies to packages/core/src/widgets/{ui.ts,types.ts,composition.ts,protocol.ts} : Treat `ui.ts`, `types.ts`, `composition.ts`, and `protocol.ts` as canonical widget factory, prop contracts, and callback signatures that must be preserved; these are safe modification zones

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-03T10:00:48.227Z
Learning: Applies to {packages/core/src/widgets/ui.ts,packages/core/src/widgets/types.ts} : Canonical API: preserve prop/callback names and signatures; these are the source of truth for widget contracts

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-06T10:44:56.248Z
Learning: Applies to **/*.{ts,tsx} : Use `defineWidget` for reusable stateful components that need composition context.

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-03T10:00:48.227Z
Learning: Read exploration order: packages/core/src/index.ts → packages/core/src/widgets/ui.ts → packages/core/src/widgets/types.ts → packages/core/src/widgets/composition.ts → packages/create-rezi/templates/ → packages/core/src/**/__tests__/

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-06T10:44:56.248Z
Learning: Applies to **/*.{ts,tsx} : Prefer semantic variants/intents over manual style-only distinctions for widget appearance.

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:10:40.638Z
Learning: Applies to **/*.{ts,tsx} : Always use `ui.*` factory functions for widget construction (ui.button, ui.text, ui.panel, etc.) — never construct raw VNode objects with `{ kind: ..., props: ... }`

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-03T10:00:48.227Z
Learning: Applies to **/*.{ts,tsx} : Avoid: importing from internal paths instead of package exports; missing id on interactive widgets; conditional hook execution; in-place mutation of app state; duplicate widget IDs in one tree

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-06T10:44:56.248Z
Learning: Applies to **/*.{ts,tsx} : Use `ui.*` factories for widget construction instead of manually creating components.

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-06T10:44:56.248Z
Learning: Applies to **/*.{ts,tsx} : Use semantic widgets (`ui.status`, `ui.badge`, `ui.tag`, `ui.callout`, `ui.spinner`, `ui.progress`) instead of custom text-based status constructs.

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:10:40.638Z
Learning: Applies to **/*.{ts,tsx} : Use `defineWidget()` for all reusable, stateful components — pass state via props, manage internal state with `ctx.useState()`

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:10:40.638Z
Learning: Applies to **/*.{ts,tsx} : All interactive widgets MUST have a unique `id` prop to ensure proper focus management and event routing

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-06T10:44:56.248Z
Learning: Applies to **/*.{ts,tsx} : Interactive widgets require unique `id` values within their scope.

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-25T05:07:29.435Z
Learning: Applies to **/*.{ts,tsx} : All interactive widgets MUST have a unique `id` prop

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-06T10:44:56.248Z
Learning: Applies to **/*.{ts,tsx} : Every interactive widget must define a unique `id` within its scope; duplicate IDs are fatal.

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-25T05:07:29.435Z
Learning: Applies to **/*.{ts,tsx} : Duplicate interactive widget IDs are fatal and will throw ZRUI_DUPLICATE_ID error

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-03T10:00:48.227Z
Learning: Risk triage: docs-only updates (low risk) need lint/grep validation; widget prop renames (medium risk) need compile + affected tests + docs parity; runtime/router/reconcile/layout/drawlist changes (high risk) need full test suite + integration coverage + PTY evidence

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-06T10:44:56.248Z
Learning: Applies to **/*.{ts,tsx} : Use `accessibleLabel` when visual labels are ambiguous or missing from interactive widgets.

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-06T10:44:56.248Z
Learning: Applies to **/*.{ts,tsx} : Use `ui.focusZone` for custom local navigation groups instead of manual focus management.

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-03T10:00:48.227Z
Learning: Applies to packages/jsx/src/{components.ts,types.ts,createElement.ts,index.ts} : Maintain JSX parity with core widget API changes; update JSX components, types, and creation functions whenever core widget APIs change

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-06T10:44:56.248Z
Learning: Applies to **/*.{ts,tsx} : Keep layout tokens and theme tokens semantic; do not hardcode RGB/hex literals in app widgets.

Learnt from: RtlZeroMemory
Repo: RtlZeroMemory/Rezi PR: 216
File: scripts/drawlist-spec.ts:1-3
Timestamp: 2026-02-26T10:29:26.658Z
Learning: In the Rezi project, public API references and documentation must consistently use the current public protocol version (v1) after the reset, and must not reference the old v6 iteration. Review tests, scripts, and docs (including scripts/drawlist-spec.ts) to replace any v6 mentions with v1, and add a short note in release/docs where protocol versions are referenced to reflect the reset.

Comment on lines +61 to +70
/** Modal width sizing. Supports fixed values, "auto", "full", `fluid(...)`, or `expr(...)`. */
width?: SizeConstraintAtom;
/** Modal height sizing. Supports fixed values, "full", `fluid(...)`, or `expr(...)` (no "auto" height). */
height?: Exclude<SizeConstraintAtom, "auto">;
/** Maximum width bound (cells, "full", `fluid(...)`, or `expr(...)`). */
maxWidth?: Exclude<SizeConstraintAtom, "auto">;
/** Minimum width bound (cells, "full", `fluid(...)`, or `expr(...)`). */
minWidth?: Exclude<SizeConstraintAtom, "auto">;
/** Minimum height bound (cells, "full", `fluid(...)`, or `expr(...)`). */
minHeight?: Exclude<SizeConstraintAtom, "auto">;
Copy link
Copy Markdown

@coderabbitai coderabbitai bot Mar 16, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Restore maxHeight in ModalProps.

This manual size surface mirrors width with minWidth/maxWidth, but height only exposes minHeight. In a split that is supposed to preserve the existing public contract, that asymmetry looks like an accidental omission and removes the non-fixed way to cap tall modal content. Based on learnings, "Canonical API: preserve prop/callback names and signatures; these are the source of truth for widget contracts."

Suggested fix
   /** Minimum height bound (cells, "full", `fluid(...)`, or `expr(...)`). */
   minHeight?: Exclude<SizeConstraintAtom, "auto">;
+  /** Maximum height bound (cells, "full", `fluid(...)`, or `expr(...)`). */
+  maxHeight?: Exclude<SizeConstraintAtom, "auto">;
   /** Frame/surface colors for modal body and border. */
   frameStyle?: OverlayFrameStyle;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/widgets/types/overlaysShell.ts` around lines 61 - 70, The
ModalProps type is missing a maxHeight mirror to maxWidth which breaks the
public contract; add a maxHeight?: Exclude<SizeConstraintAtom, "auto"> property
alongside height and minHeight in the ModalProps/overlaysShell definitions so
height bounds are symmetric (refer to the existing height, minHeight, maxWidth,
and minWidth properties to match typing and documentation).

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Verified against origin/main: ModalProps does not currently expose maxHeight, so adding it here would expand the public API. Because this PR is intentionally scoped to file splitting with an unchanged surface, I’m leaving the modal contract as-is. If we want maxHeight, that should land in a follow-up behavior/API PR.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@RtlZeroMemory, thanks for verifying against origin/main — that's the right call. My suggestion was based on an incorrect assumption that the symmetric maxHeight was part of the existing contract. Since the current API genuinely lacks it, the split correctly preserves the status quo and adding it here would be an unintended API expansion.

Would you like me to open a follow-up GitHub issue to track adding maxHeight?: Exclude<SizeConstraintAtom, "auto"> to ModalProps in a dedicated API PR?

🐇 …ears drooping slightly over a mistaken assumption…


🧠 Learnings used
Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-02-23T06:10:40.638Z
Learning: Applies to packages/core/src/layout/**/*.ts : Layout engine uses constraint-based algorithm — use `ui.box` transition properties to control which layout properties animate (defaults to position, size, opacity; constrain with explicit `properties`)

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-06T10:44:56.248Z
Learning: Prefer Layer 1 Widget API (`ui.*`) over Layer 2 Composition API for standard use cases, reserve Layer 4 VNode objects for low-level internals only.

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-03T10:00:48.227Z
Learning: Applies to {packages/core/src/widgets/ui.ts,packages/core/src/widgets/types.ts} : Canonical API: preserve prop/callback names and signatures; these are the source of truth for widget contracts

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-03T10:00:48.227Z
Learning: Applies to packages/core/src/widgets/{ui.ts,types.ts,composition.ts,protocol.ts} : Treat `ui.ts`, `types.ts`, `composition.ts`, and `protocol.ts` as canonical widget factory, prop contracts, and callback signatures that must be preserved; these are safe modification zones

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-06T10:44:56.248Z
Learning: Applies to **/*.{ts,tsx} : Use `defineWidget` for reusable stateful components that need composition context.

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-06T10:44:56.248Z
Learning: Applies to **/*.{ts,tsx} : Prefer semantic variants/intents over manual style-only distinctions for widget appearance.

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-03T10:00:48.227Z
Learning: Applies to packages/jsx/src/{components.ts,types.ts,createElement.ts,index.ts} : Maintain JSX parity with core widget API changes; update JSX components, types, and creation functions whenever core widget APIs change

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-03T10:00:48.227Z
Learning: Risk triage: docs-only updates (low risk) need lint/grep validation; widget prop renames (medium risk) need compile + affected tests + docs parity; runtime/router/reconcile/layout/drawlist changes (high risk) need full test suite + integration coverage + PTY evidence

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-06T10:44:56.248Z
Learning: Applies to **/*.{ts,tsx} : Use semantic widgets (`ui.status`, `ui.badge`, `ui.tag`, `ui.callout`, `ui.spinner`, `ui.progress`) instead of custom text-based status constructs.

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-06T10:44:56.248Z
Learning: Applies to **/*.{ts,tsx} : Use `ui.*` factories for widget construction instead of manually creating components.

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-06T10:44:56.248Z
Learning: Applies to **/*.{ts,tsx} : Group sections with `ui.panel(...)` or `ui.card(...)` instead of manually styled containers.

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-03T10:00:48.227Z
Learning: Applies to {docs/packages/jsx.md,packages/jsx/README.md,docs/getting-started/jsx.md} : Update JSX documentation to reflect API changes in core widgets

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-03T10:00:48.227Z
Learning: Applies to **/*.{ts,tsx} : Avoid: importing from internal paths instead of package exports; missing id on interactive widgets; conditional hook execution; in-place mutation of app state; duplicate widget IDs in one tree

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-06T10:44:56.248Z
Learning: Applies to **/*.{ts,tsx} : Keep layout tokens and theme tokens semantic; do not hardcode RGB/hex literals in app widgets.

Learnt from: CR
Repo: RtlZeroMemory/Rezi PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-03T10:00:48.227Z
Learning: Read exploration order: packages/core/src/index.ts → packages/core/src/widgets/ui.ts → packages/core/src/widgets/types.ts → packages/core/src/widgets/composition.ts → packages/create-rezi/templates/ → packages/core/src/**/__tests__/

Learnt from: RtlZeroMemory
Repo: RtlZeroMemory/Rezi PR: 216
File: scripts/drawlist-spec.ts:1-3
Timestamp: 2026-02-26T10:29:26.658Z
Learning: In the Rezi project, public API references and documentation must consistently use the current public protocol version (v1) after the reset, and must not reference the old v6 iteration. Review tests, scripts, and docs (including scripts/drawlist-spec.ts) to replace any v6 mentions with v1, and add a short note in release/docs where protocol versions are referenced to reflect the reset.

@RtlZeroMemory RtlZeroMemory merged commit 1757698 into main Mar 17, 2026
32 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.

1 participant