Skip to content

less store 11#29090

Merged
chrisnojima merged 16 commits intonojima/HOTPOT-next-670-cleanfrom
nojima/ZCLIENT-less-store-11
Mar 30, 2026
Merged

less store 11#29090
chrisnojima merged 16 commits intonojima/HOTPOT-next-670-cleanfrom
nojima/ZCLIENT-less-store-11

Conversation

@chrisnojima-zoom
Copy link
Copy Markdown
Contributor

No description provided.

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 continues the “zustand-store-pruning” effort by removing the settings-chat and settings-notifications Zustand stores and moving their state/RPC logic into feature-local React hooks/components in shared/settings.

Changes:

  • Removed shared/stores/settings-notifications.tsx and shared/stores/settings-chat.tsx (and their store-level tests).
  • Added useNotificationSettings hook + unit tests and rewired notifications screens to consume hook-derived props.
  • Moved chat settings (contact settings + unfurl) off stores and into shared/settings/chat.tsx local hooks.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
skill/zustand-store-pruning/references/store-checklist.md Marks settings-chat and settings-notifications store removal as complete with notes.
shared/stores/tests/settings-notifications.test.ts Removes store-based notification settings test (store removed).
shared/stores/tests/settings-chat.test.ts Removes store-based chat settings tests (store removed).
shared/stores/settings-notifications.tsx Deletes the notifications Zustand store implementation.
shared/stores/settings-chat.tsx Deletes the chat settings Zustand store implementation.
shared/settings/notifications/use-notification-settings.tsx Introduces feature-local notification settings hook + helper builders/togglers.
shared/settings/notifications/use-notification-settings.test.ts Adds unit tests for helper functions used by the new hook.
shared/settings/notifications/render.tsx Converts notifications renderer to take explicit props instead of calling a store-backed hook internally.
shared/settings/notifications/index.native.tsx Wires native notifications screen through useNotificationSettings + useNotifications props adapter.
shared/settings/notifications/index.desktop.tsx Wires desktop notifications screen through useNotificationSettings + useNotifications props adapter.
shared/settings/notifications/hooks.tsx Updates adapter hook to accept useNotificationSettings output instead of reading Zustand store.
shared/settings/group.tsx Removes dependency on deleted store type; inlines minimal setting shape for Group props.
shared/settings/chat.tsx Replaces store usage with local hooks for contact settings/unfurl and uses useNotificationSettings for security/sound/misc toggles.

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

import {loadSettings} from '../load-settings'
import {useSettingsEmailState} from '@/stores/settings-email'
import {useSettingsNotifState} from '@/stores/settings-notifications'
import type useNotificationSettings from './use-notification-settings'
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

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

useNotificationSettings is imported with import type, but the parameter type uses ReturnType<typeof useNotificationSettings>. typeof requires a value import, so this will fail type-checking. Import the hook as a value (or export an explicit return type from use-notification-settings.tsx and import that type) to fix the TS error.

Suggested change
import type useNotificationSettings from './use-notification-settings'
import useNotificationSettings from './use-notification-settings'

Copilot uses AI. Check for mistakes.
Comment on lines +229 to +265
const nextGroups = toggleNotificationGroup(groups, group, name)
setAllowEdit(false)
setGroups(nextGroups)

const f = async () => {
if (!nextGroups.get('email')) {
throw new Error('No notifications loaded yet')
}
const {JSONPayload, chatGlobalArg} = buildNotificationSavePayload(nextGroups)
const result = await new Promise<T.RPCGen.APIRes>((resolve, reject) => {
saveSubscriptionsRPC(
[
{
JSONPayload,
args: [],
endpoint: 'account/subscribe',
},
S.waitingKeySettingsGeneric,
],
resolve,
reject
)
})
await new Promise<void>((resolve, reject) => {
saveGlobalSettingsRPC(
[{settings: {...chatGlobalArg}}, S.waitingKeySettingsGeneric],
() => resolve(),
reject
)
})
if (
!result.body ||
(JSON.parse(result.body) as {status?: {code?: number}} | undefined)?.status?.code !== 0
) {
throw new Error(`Invalid response ${result.body || '(no result)'}`)
}
setAllowEdit(true)
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

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

If either save RPC fails or the response is invalid, the async f() throws and allowEdit never gets set back to true, leaving the UI stuck in a disabled state until some external refresh. Wrap the RPC sequence in try/catch/finally and ensure setAllowEdit(true) (and ideally state rollback / error UI) happens on failure.

Suggested change
const nextGroups = toggleNotificationGroup(groups, group, name)
setAllowEdit(false)
setGroups(nextGroups)
const f = async () => {
if (!nextGroups.get('email')) {
throw new Error('No notifications loaded yet')
}
const {JSONPayload, chatGlobalArg} = buildNotificationSavePayload(nextGroups)
const result = await new Promise<T.RPCGen.APIRes>((resolve, reject) => {
saveSubscriptionsRPC(
[
{
JSONPayload,
args: [],
endpoint: 'account/subscribe',
},
S.waitingKeySettingsGeneric,
],
resolve,
reject
)
})
await new Promise<void>((resolve, reject) => {
saveGlobalSettingsRPC(
[{settings: {...chatGlobalArg}}, S.waitingKeySettingsGeneric],
() => resolve(),
reject
)
})
if (
!result.body ||
(JSON.parse(result.body) as {status?: {code?: number}} | undefined)?.status?.code !== 0
) {
throw new Error(`Invalid response ${result.body || '(no result)'}`)
}
setAllowEdit(true)
const prevGroups = groups
const nextGroups = toggleNotificationGroup(groups, group, name)
setAllowEdit(false)
setGroups(nextGroups)
const f = async () => {
try {
if (!nextGroups.get('email')) {
throw new Error('No notifications loaded yet')
}
const {JSONPayload, chatGlobalArg} = buildNotificationSavePayload(nextGroups)
const result = await new Promise<T.RPCGen.APIRes>((resolve, reject) => {
saveSubscriptionsRPC(
[
{
JSONPayload,
args: [],
endpoint: 'account/subscribe',
},
S.waitingKeySettingsGeneric,
],
resolve,
reject
)
})
await new Promise<void>((resolve, reject) => {
saveGlobalSettingsRPC(
[{settings: {...chatGlobalArg}}, S.waitingKeySettingsGeneric],
() => resolve(),
reject
)
})
if (
!result.body ||
(JSON.parse(result.body) as {status?: {code?: number}} | undefined)?.status?.code !== 0
) {
throw new Error(`Invalid response ${result.body || '(no result)'}`)
}
} catch (err) {
logger.warn('Failed to save notification settings', err)
setGroups(prevGroups)
} finally {
setAllowEdit(true)
}

Copilot uses AI. Check for mistakes.
Comment on lines +166 to +270
const useNotificationSettings = () => {
const loadSubscriptionsRPC = C.useRPC(T.RPCGen.apiserverGetWithSessionRpcPromise)
const loadGlobalSettingsRPC = C.useRPC(T.RPCChat.localGetGlobalAppNotificationSettingsLocalRpcPromise)
const saveSubscriptionsRPC = C.useRPC(T.RPCGen.apiserverPostJSONRpcPromise)
const saveGlobalSettingsRPC = C.useRPC(T.RPCChat.localSetGlobalAppNotificationSettingsLocalRpcPromise)
const [allowEdit, setAllowEdit] = React.useState(false)
const [groups, setGroups] = React.useState(emptyGroups)

const refresh = React.useCallback(() => {
let handled = false
const maybeClear = async () => {
await timeoutPromise(500)
if (!handled) {
setAllowEdit(true)
setGroups(new Map())
}
}
ignorePromise(maybeClear())

const f = async () => {
let body = ''
let chatGlobalSettings: T.RPCChat.GlobalAppNotificationSettings

try {
const json = await new Promise<T.RPCGen.APIRes>((resolve, reject) => {
loadSubscriptionsRPC(
[{args: [], endpoint: 'account/subscriptions'}, S.refreshNotificationsWaitingKey],
resolve,
reject
)
})
chatGlobalSettings = await new Promise<T.RPCChat.GlobalAppNotificationSettings>((resolve, reject) => {
loadGlobalSettingsRPC([undefined, S.refreshNotificationsWaitingKey], resolve, reject)
})
body = json.body
} catch (error) {
if (!(error instanceof RPCError)) {
return
}
logger.warn(`Error getting notification settings: ${error.desc}`)
return
}

handled = true
const nextGroups = buildNotificationGroups(body, chatGlobalSettings)
if (!nextGroups) return
setAllowEdit(true)
setGroups(nextGroups)
}
ignorePromise(f())
}, [loadGlobalSettingsRPC, loadSubscriptionsRPC])

const toggle = React.useCallback(
(group: string, name?: string) => {
if (!groups.get('email')) {
logger.warn('Trying to toggle while not loaded')
return
}
if (!allowEdit) {
logger.warn('Trying to toggle while allowEdit false')
return
}

const nextGroups = toggleNotificationGroup(groups, group, name)
setAllowEdit(false)
setGroups(nextGroups)

const f = async () => {
if (!nextGroups.get('email')) {
throw new Error('No notifications loaded yet')
}
const {JSONPayload, chatGlobalArg} = buildNotificationSavePayload(nextGroups)
const result = await new Promise<T.RPCGen.APIRes>((resolve, reject) => {
saveSubscriptionsRPC(
[
{
JSONPayload,
args: [],
endpoint: 'account/subscribe',
},
S.waitingKeySettingsGeneric,
],
resolve,
reject
)
})
await new Promise<void>((resolve, reject) => {
saveGlobalSettingsRPC(
[{settings: {...chatGlobalArg}}, S.waitingKeySettingsGeneric],
() => resolve(),
reject
)
})
if (
!result.body ||
(JSON.parse(result.body) as {status?: {code?: number}} | undefined)?.status?.code !== 0
) {
throw new Error(`Invalid response ${result.body || '(no result)'}`)
}
setAllowEdit(true)
}
ignorePromise(f())
},
[allowEdit, groups, saveGlobalSettingsRPC, saveSubscriptionsRPC]
)
Copy link

Copilot AI Mar 30, 2026

Choose a reason for hiding this comment

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

The new useNotificationSettings hook now owns the refresh + toggle RPC flow (including waiting keys and sequencing apiserverPostJSONRpcPromise with localSetGlobalAppNotificationSettingsLocalRpcPromise), but the added tests only cover the pure helper functions. Add a hook-level test that mocks the RPC promises and asserts the RPCs are called with the expected payloads and that allowEdit/groups transition correctly on success and error.

Copilot uses AI. Check for mistakes.
Base automatically changed from nojima/ZCLIENT-less-store-10 to nojima/HOTPOT-next-670-clean March 30, 2026 13:46
@chrisnojima chrisnojima merged commit 8f2f7ab into nojima/HOTPOT-next-670-clean Mar 30, 2026
@chrisnojima chrisnojima deleted the nojima/ZCLIENT-less-store-11 branch March 30, 2026 14:38
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