Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Jan 28, 2026

ObjectUI field widget coverage was at 59% (19/32 types) relative to @objectstack/spec v0.3.3. This adds the 10 missing widget implementations to achieve 91% coverage.

Added Field Widgets

Input Controls

  • ColorField - Color picker with hex input/preview
  • SliderField - Range slider (configurable min/max/step)
  • RatingField - Star rating (configurable max stars)
  • CodeField - Monospace code editor

Complex Inputs

  • AvatarField - Profile picture uploader with base64 encoding
  • AddressField - Structured address (street/city/state/zip/country)
  • GeolocationField - Lat/long with browser geolocation API integration
  • SignatureField - Canvas-based signature capture

Specialized

  • QRCodeField - QR code generator/display
  • MasterDetailField - One-to-many relationship editor

Implementation

All widgets follow the FieldWidgetProps<T> interface pattern with readonly/editable mode support:

export function ColorField({ value, onChange, field, readonly }: FieldWidgetProps<string>) {
  if (readonly) {
    return (
      <div className="flex items-center gap-2">
        <div className="w-6 h-6 rounded border" style={{ backgroundColor: value }} />
        <span>{value}</span>
      </div>
    );
  }
  // ... editable mode
}

Registered in ComponentRegistry with both direct type ('color') and prefixed variant ('field:color') for consistency with existing patterns.

Coverage

Field widget implementation: 19/32 → 29/32 types (91%)

Remaining 3 types are aliases handled by existing widgets (richtext/html → RichTextField).

Original prompt

按照@objectstack/spec ui协议继续开发完成所有组件


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

@vercel
Copy link

vercel bot commented Jan 28, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
objectui Ready Ready Preview, Comment Jan 29, 2026 0:30am
objectui-components Ready Ready Preview, Comment Jan 29, 2026 0:30am
objectui-crm-app Ready Ready Preview, Comment Jan 29, 2026 0:30am

Request Review

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Copilot AI changed the title [WIP] Continue development of all components following @objectstack/spec Implement 10 missing field widgets per @objectstack/spec UI protocol Jan 28, 2026
Copilot AI requested a review from hotlong January 28, 2026 17:45
@hotlong hotlong marked this pull request as ready for review January 29, 2026 00:05
Copilot AI review requested due to automatic review settings January 29, 2026 00:05
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR implements 10 missing field widget components to improve ObjectUI's coverage of the @objectstack/spec UI protocol from 59% to 91% (29/32 field types). The new widgets provide input controls for colors, ratings, code, avatars, addresses, geolocation, signatures, and QR codes, plus a master-detail relationship editor.

Changes:

  • Added 10 new field widget components following the FieldWidgetProps<T> interface pattern with readonly/editable mode support
  • Updated component registry to include both direct type registrations ('color') and prefixed variants ('field:color')
  • Replaced placeholder LookupField registration for master_detail type with dedicated MasterDetailField implementation
  • Added basic test coverage for all new widgets

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 22 comments.

Show a summary per file
File Description
packages/fields/src/widgets/ColorField.tsx Color picker with hex input/preview and native color input
packages/fields/src/widgets/SliderField.tsx Range slider with configurable min/max/step via field metadata
packages/fields/src/widgets/RatingField.tsx Interactive star rating widget with hover preview
packages/fields/src/widgets/CodeField.tsx Monospace code editor using Textarea with language configuration
packages/fields/src/widgets/AvatarField.tsx Profile picture uploader with base64 encoding and file validation
packages/fields/src/widgets/AddressField.tsx Structured address input with street/city/state/zip/country fields
packages/fields/src/widgets/GeolocationField.tsx Lat/long input with browser geolocation API and map integration
packages/fields/src/widgets/SignatureField.tsx Canvas-based signature capture with touch/mouse support
packages/fields/src/widgets/QRCodeField.tsx QR code generator/display using external API service
packages/fields/src/widgets/MasterDetailField.tsx One-to-many relationship editor with add/remove/view actions
packages/fields/src/new-widgets.test.tsx Basic render tests for all new widgets in readonly and editable modes
packages/fields/src/index.tsx Component registry updates and exports for all new widgets

Comment on lines +15 to +18
const getQRCodeUrl = (text: string): string => {
// Using a simple QR code API service (you might want to replace this with a library)
return `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(text)}`;
};
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

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

The QRCodeField uses an external third-party API (api.qrserver.com) without HTTPS enforcement or error handling. This introduces several concerns:

  1. Security: The URL uses 'https://' but there's no guarantee the service will remain available or secure
  2. Privacy: Sensitive data in QR codes would be sent to an external service
  3. Reliability: No fallback if the API is unavailable
  4. Performance: Network latency for external API calls

Consider using a client-side QR code library instead (e.g., 'qrcode.react' or 'qrcode') to keep data processing local and ensure reliability.

Copilot uses AI. Check for mistakes.
Comment on lines +41 to +42
// This would typically navigate to the detail view
console.log('View detail:', item);
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

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

The handleView function only logs to console instead of implementing actual navigation or detail view functionality. This placeholder implementation should either be properly implemented (e.g., through an event callback prop) or clearly documented as requiring custom integration by the consumer.

Suggested change
// This would typically navigate to the detail view
console.log('View detail:', item);
/**
* Default "view" behavior:
* - If the item includes a `url` property, open it in a new tab.
* - Otherwise, no-op.
*
* Consumers that need custom navigation or detail views should either:
* - Provide a `url` field on each item, or
* - Wrap this field widget and override the click behavior externally.
*/
const url = (item as any).url;
if (typeof window !== 'undefined' && typeof url === 'string' && url.length > 0) {
window.open(url, '_blank', 'noopener,noreferrer');
}

Copilot uses AI. Check for mistakes.
const openInMaps = () => {
if (!location.latitude || !location.longitude) return;
const url = `https://www.google.com/maps?q=${location.latitude},${location.longitude}`;
window.open(url, '_blank');
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

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

The openInMaps function uses window.open with '_blank' target without setting 'noopener,noreferrer' for security. This can expose the parent window to potential security vulnerabilities through the window.opener reference. Consider using window.open(url, '_blank', 'noopener,noreferrer') to prevent the opened page from accessing the parent window.

Suggested change
window.open(url, '_blank');
window.open(url, '_blank', 'noopener,noreferrer');

Copilot uses AI. Check for mistakes.
Comment on lines +30 to +36
// Convert to base64 or upload to server
const reader = new FileReader();
reader.onloadend = () => {
onChange(reader.result as string);
};
reader.readAsDataURL(file);
};
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

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

Converting large images (up to 5MB) to base64 can significantly increase memory usage and data size (base64 encoding increases size by ~33%). Additionally, storing base64 images in component state and potentially in a database can cause performance issues. Consider:

  1. Adding a warning about base64 limitations in the documentation
  2. Optionally supporting direct file upload to a storage service
  3. Reducing the max file size or adding compression before encoding
  4. Implementing image resizing before base64 conversion to optimize for avatar use cases (typically 256x256 or similar)

Copilot uses AI. Check for mistakes.
Comment on lines +32 to +48
console.error('Geolocation is not supported by this browser');
return;
}

setIsLoading(true);
navigator.geolocation.getCurrentPosition(
(position) => {
onChange({
latitude: position.coords.latitude,
longitude: position.coords.longitude,
accuracy: position.coords.accuracy,
});
setIsLoading(false);
},
(error) => {
console.error('Error getting location:', error.message);
setIsLoading(false);
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

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

Error handling uses console.error without providing user feedback. When geolocation fails (browser doesn't support it or user denies permission), users won't see a visible error message. Consider displaying the error in the UI or leveraging the errorMessage prop available in FieldWidgetProps.

Copilot uses AI. Check for mistakes.
* Signature field widget - provides a signature pad for capturing signatures
* Outputs signature as a base64 PNG image
*/
export function SignatureField({ value, onChange, readonly }: FieldWidgetProps<string>) {
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

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

The SignatureField doesn't use the 'field' parameter at all, unlike other widgets that typically access field.placeholder, field.label, or custom properties. While this might be intentional for this specific widget, it's inconsistent with the pattern seen in other widgets. Consider whether the field prop should be used for configuration (e.g., canvas dimensions, stroke color/width) or mark it as optional in the destructuring if it's genuinely not needed.

Copilot uses AI. Check for mistakes.
*/

import { describe, it, expect } from 'vitest';
import { render, screen } from '@testing-library/react';
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

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

Unused import screen.

Suggested change
import { render, screen } from '@testing-library/react';
import { render } from '@testing-library/react';

Copilot uses AI. Check for mistakes.
{value && <AvatarImage src={value} alt={field.label} />}
<AvatarFallback>{getInitials()}</AvatarFallback>
</Avatar>
{!readonly && isHovered && value && (
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

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

This negation always evaluates to true.

Suggested change
{!readonly && isHovered && value && (
{isHovered && value && (

Copilot uses AI. Check for mistakes.
variant="outline"
size="sm"
onClick={getCurrentLocation}
disabled={readonly || isLoading}
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

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

This use of variable 'readonly' always evaluates to false.

Suggested change
disabled={readonly || isLoading}
disabled={isLoading}

Copilot uses AI. Check for mistakes.
Comment on lines +110 to +121
{!readonly && (
<Button
type="button"
variant="outline"
size="sm"
onClick={handleAdd}
className="w-full"
>
<Plus className="w-4 h-4 mr-2" />
Add Related Record
</Button>
)}
Copy link

Copilot AI Jan 29, 2026

Choose a reason for hiding this comment

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

This negation always evaluates to true.

Suggested change
{!readonly && (
<Button
type="button"
variant="outline"
size="sm"
onClick={handleAdd}
className="w-full"
>
<Plus className="w-4 h-4 mr-2" />
Add Related Record
</Button>
)}
<Button
type="button"
variant="outline"
size="sm"
onClick={handleAdd}
className="w-full"
>
<Plus className="w-4 h-4 mr-2" />
Add Related Record
</Button>

Copilot uses AI. Check for mistakes.
@hotlong hotlong merged commit 5872865 into main Jan 29, 2026
9 of 13 checks passed
@github-actions
Copy link

📦 Bundle Size Report

Package Size Gzipped
components (index.js) 1760.07KB 416.42KB
core (index.js) 0.51KB 0.28KB
data-objectstack (index.js) 4.42KB 1.47KB
fields (index.js) 75.69KB 15.58KB
layout (index.js) 12.26KB 3.83KB
plugin-aggrid (AgGridImpl-DKkq6v1B.js) 5.09KB 1.84KB
plugin-aggrid (index-B6NPAFZx.js) 15.66KB 4.51KB
plugin-aggrid (index.js) 0.18KB 0.15KB
plugin-calendar (index.js) 33.12KB 8.29KB
plugin-charts (AdvancedChartImpl-DJcN3TPx.js) 69.51KB 16.23KB
plugin-charts (BarChart-RKJxvg5Y.js) 535.74KB 134.11KB
plugin-charts (ChartImpl-CE1UGkNR.js) 8.78KB 3.11KB
plugin-charts (index-CTfEtwhn.js) 12.40KB 3.84KB
plugin-charts (index.js) 0.21KB 0.16KB
plugin-chatbot (index.js) 18.36KB 5.21KB
plugin-dashboard (index.js) 11.92KB 3.81KB
plugin-editor (MonacoImpl-B7ZgZJJG.js) 18.15KB 5.59KB
plugin-editor (index-Dl3HAAqu.js) 10.07KB 3.31KB
plugin-editor (index.js) 0.19KB 0.15KB
plugin-form (index.js) 14.43KB 4.64KB
plugin-gantt (index.js) 18.00KB 5.26KB
plugin-grid (index.js) 15.76KB 4.96KB
plugin-kanban (KanbanImpl-CUWM-JC-.js) 76.50KB 20.46KB
plugin-kanban (index-BV3FWhCb.js) 11.86KB 3.67KB
plugin-kanban (index.js) 0.18KB 0.15KB
plugin-map (index.js) 16.75KB 5.11KB
plugin-markdown (MarkdownImpl-BRkYjVWf.js) 256.79KB 64.50KB
plugin-markdown (index-D_CdfEXQ.js) 9.59KB 3.16KB
plugin-markdown (index.js) 0.19KB 0.15KB
plugin-timeline (index.js) 23.90KB 5.95KB
plugin-view (index.js) 16.24KB 4.77KB
react (SchemaRenderer.js) 1.44KB 0.73KB
react (index.js) 0.36KB 0.23KB
react (index.test.js) 0.34KB 0.26KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 0.20KB 0.18KB
types (base.js) 0.20KB 0.18KB
types (complex.js) 0.20KB 0.18KB
types (crud.js) 0.20KB 0.18KB
types (data-display.js) 0.20KB 0.18KB
types (data.js) 0.20KB 0.18KB
types (disclosure.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (index.js) 0.34KB 0.25KB
types (layout.js) 0.20KB 0.18KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (registry.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

@github-actions
Copy link

✅ All checks passed!

  • ✅ Type check passed
  • ✅ Tests passed
  • ✅ Lint check completed

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants