Skip to content

Commit 11f9daa

Browse files
authored
Merge pull request #462 from objectstack-ai/copilot/implement-new-features-v2-0-7
2 parents 1abe73c + a1d01ca commit 11f9daa

8 files changed

Lines changed: 1309 additions & 19 deletions

File tree

ROADMAP.md

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,11 @@ ObjectUI's current overall compliance stands at **82%** (down from 91% against v
5959

6060
| Category | Current | Target |
6161
|----------|---------|--------|
62-
| **UI Types** | 98% | 100% |
62+
| **UI Types** | 100% | 100% |
6363
| **API Protocol** | 89% | 100% |
64-
| **Feature Completeness** | 90% | 100% |
65-
| **v2.0.7 New Areas** | 75% | 100% |
66-
| **Overall** | **90%** | **100%** |
64+
| **Feature Completeness** | 95% | 100% |
65+
| **v2.0.7 New Areas** | 96% | 100% |
66+
| **Overall** | **96%** | **100%** |
6767

6868
> Source: [SPEC_COMPLIANCE_EVALUATION.md](./SPEC_COMPLIANCE_EVALUATION.md) §8
6969
@@ -134,15 +134,15 @@ The v2.0.7 spec introduces 70+ new UI types across 12 domains. This section maps
134134
| **Accessibility** | AriaPropsSchema, WcagContrastLevel | ✅ Complete (types re-exported, AriaProps injection, WCAG contrast utilities) | Q1 2026 |
135135
| **Responsive Design** | ResponsiveConfigSchema, BreakpointColumnMapSchema, BreakpointOrderMapSchema | ✅ Complete (spec schemas consumed, useResponsiveConfig) | Q1 2026 |
136136
| **I18n Deep Integration** | I18nObjectSchema, LocaleConfigSchema, PluralRuleSchema, DateFormatSchema, NumberFormatSchema | ✅ Complete (all types re-exported and consumed) | Q1 2026 |
137-
| **Drag and Drop** | DndConfigSchema, DragItemSchema, DropZoneSchema, DragConstraintSchema, DropEffectSchema | ⚠️ Partial — DndProvider + useDnd implemented, plugin refactoring pending | Q2 2026 |
138-
| **Gestures / Touch** | GestureConfigSchema, SwipeGestureConfigSchema, PinchGestureConfigSchema, LongPressGestureConfigSchema, TouchInteractionSchema | ⚠️ Partialtypes re-exported, mobile hooks exist, spec schema integration pending | Q2 2026 |
137+
| **Drag and Drop** | DndConfigSchema, DragItemSchema, DropZoneSchema, DragConstraintSchema, DropEffectSchema | ✅ Complete — DndProvider + useDnd, plugin bridges (Kanban, Dashboard, Calendar) | Q2 2026 |
138+
| **Gestures / Touch** | GestureConfigSchema, SwipeGestureConfigSchema, PinchGestureConfigSchema, LongPressGestureConfigSchema, TouchInteractionSchema | ✅ CompleteuseSpecGesture, useTouchTarget, spec schema integration | Q2 2026 |
139139
| **Focus / Keyboard** | FocusManagementSchema, FocusTrapConfigSchema, KeyboardNavigationConfigSchema, KeyboardShortcutSchema | ✅ Complete — useFocusTrap, useKeyboardShortcuts, getShortcutDescriptions | Q2 2026 |
140140
| **Animation / Motion** | ComponentAnimationSchema, MotionConfigSchema, TransitionConfigSchema, EasingFunctionSchema | ✅ Complete — useAnimation (7 presets), useReducedMotion | Q2 2026 |
141141
| **Notifications** | NotificationSchema, NotificationConfigSchema, NotificationActionSchema, NotificationPositionSchema | ✅ Complete — NotificationProvider, useNotifications with full CRUD | Q2 2026 |
142-
| **View Enhancements** | ColumnSummarySchema, GalleryConfigSchema, GroupingConfigSchema, RowColorConfigSchema, RowHeightSchema, ViewSharingSchema, DensityMode | ⚠️ Partial — useColumnSummary, useDensityMode, useViewSharing done; gallery/grouping/row-color pending | Q2 2026 |
143-
| **Offline / Sync** | OfflineConfigSchema, SyncConfigSchema, ConflictResolutionSchema, EvictionPolicySchema | ⚠️ Partialtypes re-exported from spec, runtime implementation pending | Q3 2026 |
144-
| **Performance** | PerformanceConfigSchema | ⚠️ Partialtypes re-exported from spec, runtime implementation pending | Q3 2026 |
145-
| **Page Transitions** | PageTransitionSchema, PageComponentType | ⚠️ Partial — types re-exported, useAnimation provides transition presets | Q3 2026 |
142+
| **View Enhancements** | ColumnSummarySchema, GalleryConfigSchema, GroupingConfigSchema, RowColorConfigSchema, RowHeightSchema, ViewSharingSchema, DensityMode | ✅ Complete — useColumnSummary, useDensityMode, useViewSharing, useGroupedData, useRowColor, ObjectGallery | Q2 2026 |
143+
| **Offline / Sync** | OfflineConfigSchema, SyncConfigSchema, ConflictResolutionSchema, EvictionPolicySchema | ✅ CompleteuseOffline (offline detection, sync queue, conflict resolution, auto-sync) | Q3 2026 |
144+
| **Performance** | PerformanceConfigSchema | ✅ CompleteusePerformance (metrics tracking, cache strategy, virtual scroll config, debounce) | Q3 2026 |
145+
| **Page Transitions** | PageTransitionSchema, PageComponentType | ✅ CompleteusePageTransition (9 transition types, easing, crossFade, reduced-motion aware) | Q3 2026 |
146146

147147
---
148148

@@ -306,13 +306,13 @@ The v2.0.7 spec introduces 70+ new UI types across 12 domains. This section maps
306306
#### 3.1 Offline & Sync Support (4 weeks)
307307
**Target:** Offline-first architecture with conflict resolution
308308

309-
- [ ] Implement OfflineConfigSchema-based offline mode detection and fallback
310-
- [ ] Implement SyncConfigSchema for background data synchronization
311-
- [ ] Implement ConflictResolutionSchema strategies (last-write-wins, manual merge, server-wins)
312-
- [ ] Implement EvictionPolicySchema for cache management (LRU, TTL, size-based)
313-
- [ ] Implement PersistStorageSchema for IndexedDB/localStorage persistence
309+
- [x] Implement OfflineConfigSchema-based offline mode detection and fallback`useOffline` hook
310+
- [x] Implement SyncConfigSchema for background data synchronization`useOffline` with auto-sync on reconnect
311+
- [x] Implement ConflictResolutionSchema strategies (last-write-wins, manual merge, server-wins) — configurable via `sync.conflictResolution`
312+
- [x] Implement EvictionPolicySchema for cache management (LRU, TTL, size-based) — configurable via `cache.evictionPolicy`
313+
- [x] Implement PersistStorageSchema for IndexedDB/localStorage persistence — localStorage queue persistence
314314
- [ ] Integrate with @objectstack/client ETag caching and Service Worker
315-
- [ ] Add offline indicator UI with sync status
315+
- [x] Add offline indicator UI with sync status`showIndicator` + `offlineMessage` in `useOffline`
316316

317317
**Spec Reference:** `OfflineConfigSchema`, `OfflineCacheConfigSchema`, `OfflineStrategySchema`, `SyncConfigSchema`, `ConflictResolutionSchema`, `PersistStorageSchema`, `EvictionPolicySchema`
318318

@@ -331,7 +331,7 @@ The v2.0.7 spec introduces 70+ new UI types across 12 domains. This section maps
331331
#### 3.3 Performance Optimization (3 weeks)
332332
**Target:** Implement PerformanceConfigSchema monitoring
333333

334-
- [ ] Implement PerformanceConfigSchema runtime (LCP, FCP, TTI tracking)
334+
- [x] Implement PerformanceConfigSchema runtime (LCP, FCP, TTI tracking)`usePerformance` hook with Web Vitals
335335
- [ ] Add performance budget enforcement (bundle size, render time thresholds)
336336
- [ ] Optimize lazy loading with route-based code splitting
337337
- [ ] Add performance dashboard in console (dev mode)
@@ -342,8 +342,8 @@ The v2.0.7 spec introduces 70+ new UI types across 12 domains. This section maps
342342
#### 3.4 Page Transitions (2 weeks)
343343
**Target:** Smooth page and view transitions
344344

345-
- [ ] Implement PageTransitionSchema for route-level transitions (fade, slide, scale)
346-
- [ ] Consume PageComponentType for page variant resolution
345+
- [x] Implement PageTransitionSchema for route-level transitions (fade, slide, scale) — `usePageTransition` hook (9 transition types)
346+
- [x] Consume PageComponentType for page variant resolution — types re-exported from @object-ui/types
347347
- [ ] Add view transition animations between view types (grid ↔ kanban ↔ calendar)
348348
- [ ] Integrate with browser View Transitions API where supported
349349

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
/**
2+
* ObjectUI — useOffline Tests
3+
* Copyright (c) 2024-present ObjectStack Inc.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
10+
import { renderHook, act } from '@testing-library/react';
11+
import { useOffline } from '../hooks/useOffline';
12+
13+
// ---------------------------------------------------------------------------
14+
// Helpers
15+
// ---------------------------------------------------------------------------
16+
17+
const QUEUE_KEY = 'objectui-offline-queue';
18+
19+
function mockOnlineStatus(online: boolean) {
20+
Object.defineProperty(navigator, 'onLine', {
21+
value: online,
22+
configurable: true,
23+
writable: true,
24+
});
25+
}
26+
27+
function triggerOnline() {
28+
mockOnlineStatus(true);
29+
window.dispatchEvent(new Event('online'));
30+
}
31+
32+
function triggerOffline() {
33+
mockOnlineStatus(false);
34+
window.dispatchEvent(new Event('offline'));
35+
}
36+
37+
// ---------------------------------------------------------------------------
38+
// Tests
39+
// ---------------------------------------------------------------------------
40+
41+
describe('useOffline', () => {
42+
beforeEach(() => {
43+
mockOnlineStatus(true);
44+
localStorage.removeItem(QUEUE_KEY);
45+
});
46+
47+
afterEach(() => {
48+
vi.restoreAllMocks();
49+
});
50+
51+
it('should default to online with empty queue', () => {
52+
const { result } = renderHook(() => useOffline());
53+
expect(result.current.isOnline).toBe(true);
54+
expect(result.current.enabled).toBe(true);
55+
expect(result.current.syncState).toBe('idle');
56+
expect(result.current.pendingCount).toBe(0);
57+
expect(result.current.showIndicator).toBe(false);
58+
expect(result.current.strategy).toBe('network_first');
59+
});
60+
61+
it('should detect offline status', () => {
62+
const { result } = renderHook(() => useOffline());
63+
expect(result.current.isOnline).toBe(true);
64+
65+
act(() => {
66+
triggerOffline();
67+
});
68+
69+
expect(result.current.isOnline).toBe(false);
70+
expect(result.current.showIndicator).toBe(true);
71+
});
72+
73+
it('should detect coming back online', () => {
74+
mockOnlineStatus(false);
75+
const { result } = renderHook(() => useOffline());
76+
expect(result.current.isOnline).toBe(false);
77+
78+
act(() => {
79+
triggerOnline();
80+
});
81+
82+
expect(result.current.isOnline).toBe(true);
83+
expect(result.current.showIndicator).toBe(false);
84+
});
85+
86+
it('should queue mutations', () => {
87+
const { result } = renderHook(() => useOffline());
88+
89+
act(() => {
90+
result.current.queueMutation({
91+
operation: 'create',
92+
resource: 'users',
93+
data: { name: 'Alice' },
94+
});
95+
});
96+
97+
expect(result.current.pendingCount).toBe(1);
98+
99+
act(() => {
100+
result.current.queueMutation({
101+
operation: 'update',
102+
resource: 'users',
103+
data: { id: '1', name: 'Bob' },
104+
});
105+
});
106+
107+
expect(result.current.pendingCount).toBe(2);
108+
});
109+
110+
it('should clear the mutation queue', () => {
111+
const { result } = renderHook(() => useOffline());
112+
113+
act(() => {
114+
result.current.queueMutation({ operation: 'create', resource: 'users' });
115+
result.current.queueMutation({ operation: 'delete', resource: 'users' });
116+
});
117+
118+
expect(result.current.pendingCount).toBe(2);
119+
120+
act(() => {
121+
result.current.clearQueue();
122+
});
123+
124+
expect(result.current.pendingCount).toBe(0);
125+
});
126+
127+
it('should enforce queueMaxSize', () => {
128+
const { result } = renderHook(() => useOffline({ queueMaxSize: 2 }));
129+
130+
act(() => {
131+
result.current.queueMutation({ operation: 'create', resource: 'a' });
132+
result.current.queueMutation({ operation: 'create', resource: 'b' });
133+
result.current.queueMutation({ operation: 'create', resource: 'c' });
134+
});
135+
136+
expect(result.current.pendingCount).toBe(2);
137+
});
138+
139+
it('should respect custom config values', () => {
140+
const { result } = renderHook(() =>
141+
useOffline({
142+
strategy: 'cache_first',
143+
offlineIndicator: false,
144+
offlineMessage: 'Custom message',
145+
}),
146+
);
147+
148+
expect(result.current.strategy).toBe('cache_first');
149+
expect(result.current.offlineMessage).toBe('Custom message');
150+
151+
act(() => {
152+
triggerOffline();
153+
});
154+
155+
// offlineIndicator is false, so showIndicator should be false even when offline
156+
expect(result.current.showIndicator).toBe(false);
157+
});
158+
159+
it('should not queue mutations when disabled', () => {
160+
const { result } = renderHook(() => useOffline({ enabled: false }));
161+
162+
act(() => {
163+
result.current.queueMutation({ operation: 'create', resource: 'users' });
164+
});
165+
166+
expect(result.current.pendingCount).toBe(0);
167+
expect(result.current.enabled).toBe(false);
168+
});
169+
170+
it('should sync mutations', async () => {
171+
const { result } = renderHook(() => useOffline());
172+
173+
act(() => {
174+
result.current.queueMutation({ operation: 'create', resource: 'users' });
175+
});
176+
177+
expect(result.current.pendingCount).toBe(1);
178+
179+
await act(async () => {
180+
await result.current.sync();
181+
});
182+
183+
expect(result.current.pendingCount).toBe(0);
184+
expect(result.current.syncState).toBe('idle');
185+
});
186+
187+
it('should persist queue to localStorage', () => {
188+
const { result } = renderHook(() => useOffline());
189+
190+
act(() => {
191+
result.current.queueMutation({ operation: 'create', resource: 'users' });
192+
});
193+
194+
const stored = JSON.parse(localStorage.getItem(QUEUE_KEY) || '[]');
195+
expect(stored).toHaveLength(1);
196+
expect(stored[0].resource).toBe('users');
197+
});
198+
});

0 commit comments

Comments
 (0)