Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# ObjectStack Protocol — Road Map

> **Last Updated:** 2026-02-22
> **Last Updated:** 2026-02-24
> **Current Version:** v3.0.8
> **Status:** Protocol Specification Complete · Runtime Implementation In Progress

Expand Down Expand Up @@ -696,6 +696,14 @@ Protocol enhancements and core component implementations for dashboard feature p
- [x] Enhance `globalFilters` with `options`, `optionsFrom`, `defaultValue`, `scope`, `targetWidgets` ([#712](https://github.com/objectstack-ai/spec/issues/712))
- [x] Add `header` configuration to `DashboardSchema` with `showTitle`, `showDescription`, `actions` ([#714](https://github.com/objectstack-ai/spec/issues/714))
- [x] Add `pivotConfig` and `measures` array to `DashboardWidgetSchema` for multi-measure pivots ([#714](https://github.com/objectstack-ai/spec/issues/714))
- [x] Add required `id` field (SnakeCaseIdentifier) to `DashboardWidgetSchema` for `targetWidgets` referencing
- [x] Unify `WidgetActionTypeSchema` with `ActionSchema.type` — add `script` and `api` types
- [x] Add `.superRefine` conditional validation to `PageSchema` (`recordReview` required for `record_review`, `blankLayout` for `blank`)
- [x] Unify easing naming in `AnimationSchema` (theme.zod) to snake_case (`ease_in`, `ease_out`, `ease_in_out`)
- [x] Add `themeToken` reference to `TransitionConfigSchema` for theme animation token binding
- [x] Add `ResponsiveConfigSchema` and `PerformanceConfigSchema` to `ListViewSchema`
- [x] Migrate `HttpMethodSchema` / `HttpRequestSchema` from `view.zod.ts` to `shared/http.zod.ts` (re-exported for backward compat)
- [x] Rename `ThemeMode`→`ThemeModeSchema`, `DensityMode`→`DensityModeSchema`, `WcagContrastLevel`→`WcagContrastLevelSchema` (deprecated aliases kept)

**ObjectUI Component Implementations:**
- [ ] Implement `DashboardFilterBar` component for global filters ([objectui#588](https://github.com/objectstack-ai/objectui/issues/588))
Expand Down
43 changes: 43 additions & 0 deletions packages/spec/src/shared/http.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { describe, it, expect } from 'vitest';
import {
HttpMethod,
HttpMethodSchema,
HttpRequestSchema,
CorsConfigSchema,
RateLimitConfigSchema,
StaticMountSchema,
Expand Down Expand Up @@ -134,3 +136,44 @@ describe('StaticMountSchema', () => {
expect(() => StaticMountSchema.parse({ path: '/static' })).toThrow();
});
});

// ============================================================================
// Issue #8: HttpMethodSchema and HttpRequestSchema migrated to shared
// ============================================================================
describe('HttpMethodSchema (migrated from view.zod)', () => {
it('should accept common HTTP methods', () => {
const valid = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'];
valid.forEach(m => {
expect(HttpMethodSchema.parse(m)).toBe(m);
});
});

it('should reject HEAD and OPTIONS (subset for UI data sources)', () => {
expect(() => HttpMethodSchema.parse('HEAD')).toThrow();
expect(() => HttpMethodSchema.parse('OPTIONS')).toThrow();
});
});

describe('HttpRequestSchema (migrated from view.zod)', () => {
it('should accept minimal request with url only', () => {
const result = HttpRequestSchema.parse({ url: 'https://api.example.com/data' });
expect(result.url).toBe('https://api.example.com/data');
expect(result.method).toBe('GET');
});

it('should accept full request with all fields', () => {
const result = HttpRequestSchema.parse({
url: 'https://api.example.com/data',
method: 'POST',
headers: { 'Authorization': 'Bearer token' },
params: { page: 1 },
body: { name: 'test' },
});
expect(result.method).toBe('POST');
expect(result.headers?.['Authorization']).toBe('Bearer token');
});

it('should reject request without url', () => {
expect(() => HttpRequestSchema.parse({})).toThrow();
});
});
24 changes: 24 additions & 0 deletions packages/spec/src/shared/http.zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,30 @@ export const HttpMethod = z.enum([

export type HttpMethod = z.infer<typeof HttpMethod>;

/**
* HTTP Method Schema (subset for UI/View data sources)
* Common HTTP methods used in view data source configurations.
* Migrated from ui/view.zod.ts to shared for reuse across modules.
*/
export const HttpMethodSchema = z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE']);

export type HttpMethodType = z.infer<typeof HttpMethodSchema>;

/**
* HTTP Request Configuration Schema
* Defines a complete HTTP request configuration used by API data providers.
* Migrated from ui/view.zod.ts to shared for reuse across modules.
*/
export const HttpRequestSchema = z.object({
url: z.string().describe('API endpoint URL'),
method: HttpMethodSchema.optional().default('GET').describe('HTTP method'),
headers: z.record(z.string(), z.string()).optional().describe('Custom HTTP headers'),
params: z.record(z.string(), z.unknown()).optional().describe('Query parameters'),
body: z.unknown().optional().describe('Request body for POST/PUT/PATCH'),
});

export type HttpRequest = z.infer<typeof HttpRequestSchema>;

// ==========================================
// CORS Configuration
// ==========================================
Expand Down
6 changes: 3 additions & 3 deletions packages/spec/src/stack.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1013,8 +1013,8 @@ describe('defineStack - Example-Level Strict Validation', () => {
name: 'task_overview',
label: 'Task Overview',
widgets: [
{ title: 'Total Tasks', type: 'metric', object: 'task', aggregate: 'count', layout: { x: 0, y: 0, w: 3, h: 2 } },
{ title: 'By Status', type: 'pie', object: 'task', categoryField: 'status', aggregate: 'count', layout: { x: 3, y: 0, w: 6, h: 4 } },
{ id: 'total_tasks', title: 'Total Tasks', type: 'metric', object: 'task', aggregate: 'count', layout: { x: 0, y: 0, w: 3, h: 2 } },
{ id: 'by_status', title: 'By Status', type: 'pie', object: 'task', categoryField: 'status', aggregate: 'count', layout: { x: 3, y: 0, w: 6, h: 4 } },
],
},
],
Expand Down Expand Up @@ -1094,7 +1094,7 @@ describe('defineStack - Example-Level Strict Validation', () => {
name: 'sales_overview',
label: 'Sales Overview',
widgets: [
{ title: 'Pipeline Value', type: 'metric', object: 'opportunity', valueField: 'amount', aggregate: 'sum', layout: { x: 0, y: 0, w: 4, h: 2 } },
{ id: 'pipeline_value', title: 'Pipeline Value', type: 'metric', object: 'opportunity', valueField: 'amount', aggregate: 'sum', layout: { x: 0, y: 0, w: 4, h: 2 } },
],
},
],
Expand Down
28 changes: 28 additions & 0 deletions packages/spec/src/ui/animation.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,3 +235,31 @@ describe('I18n and ARIA integration', () => {
expect(result.role).toBeUndefined();
});
});

// ============================================================================
// Issue #6: TransitionConfigSchema themeToken support
// ============================================================================
describe('TransitionConfigSchema - themeToken', () => {
it('should accept transition with themeToken reference', () => {
const result = TransitionConfigSchema.parse({
themeToken: 'animation.duration.fast',
});
expect(result.themeToken).toBe('animation.duration.fast');
});

it('should accept transition combining themeToken with explicit values', () => {
const result = TransitionConfigSchema.parse({
preset: 'fade',
duration: 200,
easing: 'ease_in_out',
themeToken: 'animation.timing.ease_in_out',
});
expect(result.themeToken).toBe('animation.timing.ease_in_out');
expect(result.duration).toBe(200);
});

it('should leave themeToken undefined when not provided', () => {
const result = TransitionConfigSchema.parse({ duration: 300 });
expect(result.themeToken).toBeUndefined();
});
});
1 change: 1 addition & 0 deletions packages/spec/src/ui/animation.zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export const TransitionConfigSchema = z.object({
easing: EasingFunctionSchema.optional().describe('Easing function for the transition'),
delay: z.number().optional().describe('Delay before transition starts in milliseconds'),
customKeyframes: z.string().optional().describe('CSS @keyframes name for custom animations'),
themeToken: z.string().optional().describe('Reference to a theme animation token (e.g. "animation.duration.fast")'),
}).describe('Animation transition configuration');

export type TransitionConfig = z.infer<typeof TransitionConfigSchema>;
Expand Down
Loading