Disciplr utilizes Zustand to manage global client-side state across two main stores:
useVerifierStore: Manages validation tasks (pending queue and verification history) for verifiers.useNotification: Manages user notifications, unread status, and batch read operations.
This document describes the state structures, the transition mechanics, and recommendations for React components consuming these stores.
The verifier store is defined in Store.ts and manages verification workflows.
Each validation task conforms to the following type structure:
| Field | Type | Description |
|---|---|---|
id |
string |
Unique identifier for the validation task. |
vaultName |
string |
Name of the vault associated with the validation. |
owner |
string |
Address or identity of the vault owner. |
amount |
string |
Formatted amount locked or requested (e.g., '50,000 USDC'). |
deadline |
string |
ISO Date string representing the task deadline. |
daysRemaining |
number |
Computed days remaining until the deadline. |
status |
'pending' | 'approved' | 'rejected' |
Current verification status. |
milestone |
string |
Name or description of the milestone. |
evidenceUrl |
string (optional) |
URL pointing to validation evidence (e.g. GitHub release, Figma mockup). |
notes |
string (optional) |
Explanatory notes provided by the verifier during decision. |
criteria |
string[] (optional) |
An array of specific requirements that must be verified. |
pendingValidations: ValidationTask[]An array containing all active validation tasks. The status field of all tasks in this list is'pending'.validationHistory: ValidationTask[]An array containing historically resolved validation tasks. The status of tasks in this list is either'approved'or'rejected'.
When a verifier resolves a validation task, the task transitions from pendingValidations to validationHistory:
- The task is located in the
pendingValidationsqueue by itsid. - Its status is updated to
'approved'or'rejected', and the verifier's optionalnotesare attached. - The task is removed from the
pendingValidationsarray. - The updated task is prepended to the beginning (index
0) of thevalidationHistoryarray, ensuring the most recent decisions appear first.
- Effect: Transition the task with the given
idfrompendingtohistorywithstatus: 'approved'. - Edge Case: If the ID is not found in
pendingValidations, the operation is a no-op (no state change).
- Effect: Transition the task with the given
idfrompendingtohistorywithstatus: 'rejected'. - Edge Case: If the ID is not found in
pendingValidations, the operation is a no-op (no state change).
- Effect: Resolves multiple validation tasks as
'approved'with the specified notes. - Implementation & Semantics: Batch actions iterate over the provided
idslist and callapproveValidationsequentially. Thus, the transition and state consistency remain identical to individual approvals. - Edge Cases:
- Any unknown or already-resolved IDs in the
idsarray are ignored (no-op). - An empty
idslist is a safe no-op.
- Any unknown or already-resolved IDs in the
- Effect: Resolves multiple validation tasks as
'rejected'with the specified notes. - Implementation & Semantics: Sequentially invokes
rejectValidationfor each ID in the array. - Edge Cases:
- Any unknown or already-resolved IDs in the
idsarray are ignored (no-op). - An empty
idslist is a safe no-op.
- Any unknown or already-resolved IDs in the
The notification store manages client-side notifications and unread badges.
Derived from example notifications, each item has the following structure:
| Field | Type | Description |
|---|---|---|
id |
string |
Unique identifier for the notification. |
type |
string |
The notification category (e.g. 'vault_deadline_approaching', 'funds_released', 'verification_requested'). |
isUrgent |
boolean |
Flag indicating whether this requires immediate attention. |
title |
string |
Header text/subject of the notification. |
message |
string |
Detailed message description. |
timestamp |
string |
ISO 8601 formatted timestamp of the event. |
timeAgo |
string |
Short relative time label (e.g., '2m ago', 'Yesterday'). |
isRead |
boolean |
Tracking status indicating whether the notification was read. |
category |
string |
Broad categorization tag (e.g. 'vault', 'funds', 'verification', 'system'). |
notification: NotificationItem[]The list of all notifications currently loaded into memory.unreadCount: numberThe quantity of items innotificationwhereisReadisfalse.
- Effect: Replaces the list of notifications entirely and recalculates
unreadCountbased on the new array.
- Effect: Locates the notification by its
id. If found andisReadisfalse, it setsisReadtotrueand decrementsunreadCountby 1. - Idempotence: Calling
markReadmultiple times for the sameidhas no additional effect and will not decrementunreadCountbelow0. - Edge Cases: If the ID is not found, it is a safe no-op.
- Effect: Iterates through all notifications, updating any item with
isRead: falsetoisRead: true. ResetsunreadCountto0. - Idempotence: Can be called repeatedly; if all notifications are already read, state remains unchanged.
To avoid unnecessary component re-renders when using Zustand, always select specific state slices rather than consuming the entire store object.
Use selector functions to extract only the state and actions required by the component. This ensures the component only re-renders when the selected slice changes.
import React from 'react';
import { useVerifierStore } from '@/Zustand/Store';
export const PendingQueueCount: React.FC = () => {
// Good: Re-renders only when pendingValidations.length changes
const pendingCount = useVerifierStore((state) => state.pendingValidations.length);
return (
<div className="badge">
Pending Tasks: {pendingCount}
</div>
);
};
export const ApproveButton: React.FC<{ taskId: string }> = ({ taskId }) => {
// Good: Component never re-renders when store state changes because actions are stable references
const approveValidation = useVerifierStore((state) => state.approveValidation);
return (
<button onClick={() => approveValidation(taskId, "Looks good!")}>
Approve Task
</button>
);
};Avoid destructuring the hook call without selectors, as it causes the component to re-render on any change within the store, even if the fields you destructured didn't change:
// ⚠️ Avoid this pattern:
const { pendingValidations, approveValidation } = useVerifierStore();
// This component will re-render whenever *any* field changes (e.g. validationHistory, unreadCount, etc.)