Skip to content

Phase 4B: Views API refactor, permissions, workflow, realtime, notifications#25

Merged
hotlong merged 5 commits intomainfrom
copilot/refactor-views-api-permissions-workflow
Feb 9, 2026
Merged

Phase 4B: Views API refactor, permissions, workflow, realtime, notifications#25
hotlong merged 5 commits intomainfrom
copilot/refactor-views-api-permissions-workflow

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Feb 9, 2026

Implements all Phase 4B ObjectOS integration features using the SDK v2.0.1 typed APIs. Five subsystems: views refactor, permissions, workflow/approval, realtime subscriptions, and push notifications.

4B.1 — Views API Refactor

  • Removed (client as any).views workaround in useViewStorage.ts; now uses typed client.views.* API directly
  • SavedView type extended with list/form view config fields matching SDK's ListViewsResponse
  • Backward-compatible: legacy flat filters/sort/columns inputs auto-wrapped into list config

4B.2 — Permission System

  • New hooks/usePermissions.ts — wraps client.permissions.getObjectPermissions() and .check()
  • Permission-aware props added to renderers:
    • ListViewRenderer: allowCreate
    • DetailViewRenderer: allowEdit, allowDelete
    • FormViewRenderer: fieldPermissions (per-field { readable, editable })
    • ActionBar: disabledActions set to filter unauthorized actions

4B.3 — Workflow & Approval

  • New hooks/useWorkflowState.tsgetState(), transition(), approve(), reject() with auto-refetch
  • New components/workflow/WorkflowStatePanel.tsx — state badge, transition buttons, approve/reject with comment input, collapsible history timeline

4B.4 — Real-time Updates

  • New hooks/useSubscription.ts — connection lifecycle, channel subscriptions, presence via client.realtime.*
  • New components/realtime/CollaborationIndicator.tsx — avatar row showing who's viewing/editing

4B.5 — Push Notifications

  • New hooks/useNotifications.ts — list, markRead, markAllRead, registerDevice, preferences CRUD
  • app/(tabs)/notifications.tsx upgraded from empty stub to full notification list with read/unread state, mark-all-read, and deep linking via actionUrl

Tests

  • 29 new hook tests across 5 test files (375 total, all passing)
// Example: permission-aware detail view
const { permissions } = usePermissions("tasks");

<DetailViewRenderer
  record={record}
  onEdit={handleEdit}
  onDelete={handleDelete}
  allowEdit={permissions?.allowEdit ?? true}
  allowDelete={permissions?.allowDelete ?? true}
/>

💡 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.

Copilot AI and others added 4 commits February 9, 2026 13:13
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
… workflow, realtime, notifications

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
…nt comments, improve type safety

Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Copilot AI changed the title [WIP] Refactor Views API and implement permissions system Phase 4B: Views API refactor, permissions, workflow, realtime, notifications Feb 9, 2026
Copilot AI requested a review from hotlong February 9, 2026 13:31
@hotlong hotlong marked this pull request as ready for review February 9, 2026 13:39
Copilot AI review requested due to automatic review settings February 9, 2026 13:39
@hotlong hotlong merged commit b5b095d into main Feb 9, 2026
Copy link
Copy Markdown

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 completes Phase 4B ObjectOS integration by adopting the SDK v2.0.1 typed APIs and adding end-to-end support for views, permissions, workflows, realtime, and notifications across hooks, UI components, and documentation.

Changes:

  • Refactors saved views storage to use typed client.views.* APIs and supports list/form view configs (with legacy flat filter/sort/columns mapping).
  • Introduces new hooks for permissions, workflow state, realtime subscriptions/presence, and notifications (plus corresponding UI/screens).
  • Updates roadmap/project status docs and expands hook test coverage.

Reviewed changes

Copilot reviewed 19 out of 20 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
hooks/useWorkflowState.ts New hook wrapping client.workflow.* for state/transition/approve/reject with refetch.
hooks/useViewStorage.ts Refactor to typed client.views.*, add list/form config support + legacy mapping helpers.
hooks/useSubscription.ts New realtime hook for connect/subscribe + presence helpers.
hooks/usePermissions.ts New hook for object/field permissions and permission checks.
hooks/useNotifications.ts New notifications hook: list/pagination, mark read, device registration, preferences.
components/workflow/WorkflowStatePanel.tsx Workflow UI panel for state badge, transitions, approval actions, and history.
components/renderers/ListViewRenderer.tsx Adds allowCreate prop (permission-related).
components/renderers/FormViewRenderer.tsx Adds fieldPermissions and applies editable to readonly logic.
components/renderers/DetailViewRenderer.tsx Adds allowEdit/allowDelete and gates action handlers accordingly.
components/realtime/CollaborationIndicator.tsx Presence-based collaboration indicator component.
components/actions/ActionBar.tsx Adds disabledActions filtering to hide unauthorized actions.
app/(tabs)/notifications.tsx Implements notifications list UI with read/unread state, mark-all-read, and deep linking.
tests/hooks/useWorkflowState.test.ts Tests workflow hook fetch/transition/approve/reject.
tests/hooks/useViewStorage.test.ts Tests typed views API integration + legacy mapping behavior.
tests/hooks/useSubscription.test.ts Tests realtime connect/subscribe and presence helpers.
tests/hooks/usePermissions.test.ts Tests permission fetching and checkPermission helper.
tests/hooks/useNotifications.test.ts Tests notifications list, read management, device registration, preferences, pagination.
docs/ROADMAP.md Marks Phase 4B.1–4B.5 as completed.
docs/PROJECT-STATUS.md Updates Phase 4B status to COMPLETE and refreshes test counts/status text.
.gitignore Ignores package-lock.json (pnpm repo).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +108 to +110
const isFieldReadonly = readonly || meta.readonly ||
(fieldPermissions?.[fieldDef.name] && !fieldPermissions[fieldDef.name].editable);

Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

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

Field-level permissions include readable, but the renderer only uses editable to decide read-only state. As-is, fields with { readable: false } will still be rendered (data exposure) and only made read-only. Consider filtering such fields out (or rendering a placeholder) when readable is false.

Copilot uses AI. Check for mistakes.
Comment thread hooks/useSubscription.ts
Comment on lines +81 to +84
// Keep callback ref stable
const onEventRef = useRef(onEvent);
onEventRef.current = onEvent;

Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

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

onEvent is accepted and stored in a ref, but it is never invoked anywhere in the hook. Either remove the option or wire it to the SDK’s event stream so callers actually receive realtime events.

Copilot uses AI. Check for mistakes.
Comment thread hooks/useSubscription.ts
Comment on lines +152 to +163
// Auto-connect on mount if enabled; reconnect only when enabled/channel change.
// doConnect/doDisconnect are intentionally omitted to avoid re-subscribing on
// every render when the events array or callbacks change.
useEffect(() => {
if (enabled) {
void doConnect();
}
return () => {
void doDisconnect();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [enabled, channel]);
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

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

The effect intentionally omits doDisconnect from the dependency list, but that causes the cleanup to capture a stale subscriptionId (usually null from first render). On unmount/channel change this will skip unsubscribe(subscriptionId) and can leak server-side subscriptions. Use a ref for the latest subscriptionId/disconnect handler or include doDisconnect in deps and restructure to avoid resubscribe loops.

Copilot uses AI. Check for mistakes.
Comment thread hooks/useSubscription.ts
Comment on lines +88 to +106
const result: RealtimeConnectResponse = await client.realtime.connect({
transport: "websocket",
channels: [channel],
});
setConnectionId(result.connectionId);
setIsConnected(true);

// Subscribe to the channel
const sub: RealtimeSubscribeResponse = await client.realtime.subscribe({
channel,
events,
});
setSubscriptionId(sub.subscriptionId);
} catch (err) {
setError(
err instanceof Error ? err : new Error("Realtime connection failed"),
);
setIsConnected(false);
}
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

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

If client.realtime.subscribe() throws after a successful connect(), the catch block sets isConnected false but never disconnects the underlying realtime connection. Ensure you clean up (unsubscribe/disconnect) on partial failures to avoid leaving a live connection around.

Copilot uses AI. Check for mistakes.
Comment thread hooks/useNotifications.ts
Comment on lines +138 to +141
setNotifications((prev) =>
prev.map((n) => (ids.includes(n.id) ? { ...n, read: true } : n)),
);
setUnreadCount((prev) => Math.max(0, prev - ids.length));
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

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

unreadCount is decremented by ids.length regardless of whether those notifications were previously unread or even present in state. This can desync the count from reality. Prefer recomputing unreadCount from the updated notifications list, or use the server response (e.g., readCount/unreadCount) if available.

Suggested change
setNotifications((prev) =>
prev.map((n) => (ids.includes(n.id) ? { ...n, read: true } : n)),
);
setUnreadCount((prev) => Math.max(0, prev - ids.length));
setNotifications((prev) => {
const updated = prev.map((n) =>
ids.includes(n.id) ? { ...n, read: true } : n,
);
const newUnreadCount = updated.reduce(
(count, n) => (n.read ? count : count + 1),
0,
);
setUnreadCount(newUnreadCount);
return updated;
});

Copilot uses AI. Check for mistakes.
Comment on lines +55 to +63
onPress,
}: {
transition: WorkflowTransition;
onPress: (t: WorkflowTransition) => void;
}) {
return (
<Pressable
className="flex-row items-center gap-1.5 rounded-lg border border-border px-3 py-2 active:bg-muted/50"
onPress={() => onPress(transition)}
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

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

While an action is in flight (isActing), transition buttons remain enabled, so users can trigger duplicate transitions rapidly. Disable the TransitionButton pressable (and optionally approve/reject) when isActing is true.

Suggested change
onPress,
}: {
transition: WorkflowTransition;
onPress: (t: WorkflowTransition) => void;
}) {
return (
<Pressable
className="flex-row items-center gap-1.5 rounded-lg border border-border px-3 py-2 active:bg-muted/50"
onPress={() => onPress(transition)}
onPress,
isActing,
}: {
transition: WorkflowTransition;
onPress: (t: WorkflowTransition) => void;
isActing?: boolean;
}) {
return (
<Pressable
className="flex-row items-center gap-1.5 rounded-lg border border-border px-3 py-2 active:bg-muted/50"
onPress={() => onPress(transition)}
disabled={isActing}

Copilot uses AI. Check for mistakes.
Comment on lines +67 to +68
/** Permission: hide create button when false */
allowCreate?: boolean;
Copy link

Copilot AI Feb 9, 2026

Choose a reason for hiding this comment

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

allowCreate is declared in props but never read/used (and is not referenced anywhere in this component). Either wire it into the list UI (e.g., hide/disable the create affordance) or remove the prop to avoid a misleading API surface.

Suggested change
/** Permission: hide create button when false */
allowCreate?: boolean;

Copilot uses AI. Check for mistakes.
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.

3 participants