Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
0db2c7a
fs + convostate cleanup (#29012)
chrisnojima-zoom Mar 12, 2026
363661c
fix bugs in teams store
chrisnojima-zoom Mar 12, 2026
4ce23a4
simplify chat store: fix logger bug, remove redundant casts, skip no-…
chrisnojima-zoom Mar 12, 2026
43a3c1a
simplify config store: merge set calls, fix network change detection,…
chrisnojima-zoom Mar 12, 2026
207c1eb
simplify profile store: merge router imports, merge set calls, dedup …
chrisnojima-zoom Mar 12, 2026
561499d
simplify tracker store: remove username alias, fix dead code, dedupli…
chrisnojima Mar 12, 2026
8cf4d23
simplify crypto store: fix double encrypt, remove dead code and misle…
chrisnojima Mar 12, 2026
27adad9
simplify provision store: remove dead state, extract cancel helpers, …
chrisnojima Mar 12, 2026
e249fde
fix lint/tsc: eliminate always-falsy flags, fix optional chain in stores
chrisnojima Mar 12, 2026
e227a1d
simplify people store: remove dead code, fix O(n²) spreads, single-pa…
chrisnojima Mar 12, 2026
acc90ab
simplify team-building store: remove dead state, deduplicate work
chrisnojima Mar 12, 2026
702c4a8
remove dead serviceResultCount from team-building Props type
chrisnojima Mar 12, 2026
be76372
simplify archive store: fix type typo, race conditions, unsafe side e…
chrisnojima Mar 12, 2026
a3aca1d
simplify push store: fix Promise anti-pattern, extract token helper, …
chrisnojima Mar 12, 2026
aecc6c9
simplify signup store: prune dead state/fields, remove restartSignup …
chrisnojima Mar 12, 2026
4441d59
simplify daemon store: batch set calls, inline helpers, remove dead b…
chrisnojima Mar 12, 2026
6febf12
simplify settings store: extract pprof helper, fix setProxyData state…
chrisnojima Mar 12, 2026
e961b4e
simplify users store: merge duplicate imports, batch identity updates…
chrisnojima Mar 12, 2026
75de866
archive: replace WebCrypto job ID with cross-platform makeUUID
chrisnojima-zoom Mar 12, 2026
d7bcf92
WIP
chrisnojima Mar 13, 2026
d95ba73
fix lint and tsc: add async to pprof callbacks, add makeUUID import t…
chrisnojima Mar 13, 2026
ae796a4
fix exploding state
chrisnojima Mar 13, 2026
4180fe5
lint hook
chrisnojima Mar 13, 2026
4bc5e9b
remove path
chrisnojima Mar 13, 2026
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
24 changes: 24 additions & 0 deletions .claude/hooks/pre-commit-check.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/bash

INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command')

if echo "$COMMAND" | grep -qE "git commit"; then
REPO_ROOT=$(git -C "$(dirname "$0")" rev-parse --show-toplevel)

echo "Running yarn lint..." >&2
if ! (cd "$REPO_ROOT/shared" && yarn lint 2>&1); then
echo "Lint failed — commit blocked." >&2
exit 2
fi

echo "Running yarn tsc..." >&2
if ! (cd "$REPO_ROOT/shared" && yarn tsc 2>&1); then
echo "TypeScript check failed — commit blocked." >&2
exit 2
fi

echo "Pre-commit checks passed." >&2
fi

exit 0
34 changes: 34 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"permissions": {
"allow": [
"Bash(yarn lint)",
"Bash(yarn lint-warn:*)",
"Bash(yarn prettier:*)",
"Bash(yarn build-*)",
"Bash(yarn package)",
"Bash(yarn start*)",
"Bash(yarn rn-gobuild*)",
"Bash(yarn rn-download-android*)",
"Bash(yarn tsc)",
"Bash(yarn tsc:*)",
"Bash(yarn pod*)",
"Bash(yarn coverage:*)",
"Bash(yarn maestro*)"
]
},
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": ".claude/hooks/pre-commit-check.sh",
"timeout": 120,
"statusMessage": "Running lint and tsc..."
}
]
}
]
}
}
12 changes: 2 additions & 10 deletions shared/chat/conversation/input-area/normal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -151,9 +151,7 @@ const ConnectedPlatformInput = function ConnectedPlatformInput() {
? styles.suggestionOverlayInfoShowing
: styles.suggestionOverlay

const allowExplodingModeRef = React.useRef(-1)
const setExplodingMode = (mode: number) => {
allowExplodingModeRef.current = mode
setExplodingModeRaw(mode, false)
}

Expand Down Expand Up @@ -249,14 +247,8 @@ const ConnectedPlatformInput = function ConnectedPlatformInput() {
}, [setInputRef])

React.useEffect(() => {
if (explodingModeSeconds !== explodingModeSecondsRaw) {
// ignore if we have text unless we set it ourselves
if (!textValueRef.current || allowExplodingModeRef.current === explodingModeSecondsRaw) {
allowExplodingModeRef.current = -1
setExplodingModeSeconds(explodingModeSecondsRaw)
}
}
}, [explodingModeSeconds, explodingModeSecondsRaw])
setExplodingModeSeconds(explodingModeSecondsRaw)
}, [explodingModeSecondsRaw])

return (
<PlatformInput
Expand Down
2 changes: 2 additions & 0 deletions shared/constants/init/shared.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -771,6 +771,8 @@ export const _onEngineIncoming = (action: EngineGen.Actions) => {
{
const {useTeamsState} = require('@/stores/teams') as typeof UseTeamsStateType
useTeamsState.getState().dispatch.onEngineIncomingImpl(action)
const {useChatState} = require('@/stores/chat') as typeof UseChatStateType
useChatState.getState().dispatch.onEngineIncomingImpl(action)
}
break
case EngineGen.keybase1NotifyFeaturedBotsFeaturedBotsUpdate:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"
askForAppToLaunch = "Yes"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
Expand Down
4 changes: 2 additions & 2 deletions shared/signup/username.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ const ConnectedEnterUsername = () => {
const checkUsername = useSignupState(s => s.dispatch.checkUsername)
const waiting = C.Waiting.useAnyWaiting(C.waitingKeySignup)
const navigateUp = C.useRouterState(s => s.dispatch.navigateUp)
const restartSignup = useSignupState(s => s.dispatch.restartSignup)
const resetState = useSignupState(s => s.dispatch.resetState)
const onBack = () => {
restartSignup()
resetState()
navigateUp()
}
const onContinue = checkUsername
Expand Down
92 changes: 43 additions & 49 deletions shared/stores/archive.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {ignorePromise} from '@/constants/utils'
import * as EngineGen from '@/actions/engine-gen-gen'
import {pathToRPCPath} from '@/constants/fs'
import {formatTimeForPopup} from '@/util/timestamp'
import {uint8ArrayToHex} from 'uint8array-extras'
import {makeUUID} from '@/util/uuid'
import {fsCacheDir, isAndroid, isMobile} from '@/constants/platform'

type ChatJob = {
Expand All @@ -17,7 +17,7 @@ type ChatJob = {
status: T.RPCChat.ArchiveChatJobStatus
}

type KBFSJobPhase = 'Queued' | 'Indexing' | 'Indexed' | 'Copying' | 'Copyied' | 'Zipping' | 'Done'
type KBFSJobPhase = 'Queued' | 'Indexing' | 'Indexed' | 'Copying' | 'Copied' | 'Zipping' | 'Done'

type KBFSJob = {
id: string
Expand Down Expand Up @@ -95,7 +95,7 @@ export const useArchiveState = Z.createZustand<State>('archive', (set, get) => {
set(s => {
s.kbfsJobs = new Map(
// order is retained
(status.jobs || []).map(job => [
(status.jobs ?? []).map(job => [
job.desc.jobID,
{
bytesCopied: job.bytesCopied,
Expand Down Expand Up @@ -125,19 +125,26 @@ export const useArchiveState = Z.createZustand<State>('archive', (set, get) => {
} as KBFSJob,
])
)
for (const id of s.kbfsJobsFreshness.keys()) {
if (!s.kbfsJobs.has(id)) {
s.kbfsJobsFreshness.delete(id)
}
}
})
}

const setChatProgress = (p: {jobID: string; messagesComplete: number; messagesTotal: number}) => {
const {jobID, messagesComplete, messagesTotal} = p
if (!get().chatJobs.has(jobID)) {
loadChat()
return
}
set(s => {
const job = s.chatJobs.get(jobID)
if (!job) {
loadChat()
return
if (job) {
job.progress = messagesTotal ? messagesComplete / messagesTotal : 0
job.status = T.RPCChat.ArchiveChatJobStatus.running
}
job.progress = messagesTotal ? messagesComplete / messagesTotal : 0
job.status = T.RPCChat.ArchiveChatJobStatus.running
})
}

Expand Down Expand Up @@ -169,8 +176,7 @@ export const useArchiveState = Z.createZustand<State>('archive', (set, get) => {

const startChatArchive = (query: T.RPCChat.GetInboxLocalQuery | null, outPath: string) => {
const f = async () => {
const jobID = Uint8Array.from([...Array<number>(8)], () => Math.floor(Math.random() * 256))
const id = uint8ArrayToHex(jobID)
const id = makeUUID()
const actualOutPath = outPath || (isAndroid && fsCacheDir ? `${fsCacheDir}/kbchat-${id}` : '')
Comment on lines 177 to 180
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

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

crypto.getRandomValues assumes a global WebCrypto implementation is available. In React Native (and some Electron/Node contexts) crypto may be undefined, causing a runtime crash when starting a chat archive job. Consider switching to an existing cross-platform ID generator used elsewhere in the app (or adding a guarded fallback) so job ID generation works reliably across mobile + desktop.

Copilot uses AI. Check for mistakes.
try {
await T.RPCChat.localArchiveChatRpcPromise({
Expand All @@ -196,8 +202,8 @@ export const useArchiveState = Z.createZustand<State>('archive', (set, get) => {
}

const clearCompletedChat = () => {
ignorePromise(
Promise.allSettled(
const f = async () => {
await Promise.allSettled(
[...get().chatJobs.values()].map(async job => {
if (job.status === T.RPCChat.ArchiveChatJobStatus.complete) {
await T.RPCChat.localArchiveChatDeleteRpcPromise({
Expand All @@ -208,21 +214,23 @@ export const useArchiveState = Z.createZustand<State>('archive', (set, get) => {
}
})
)
)
loadChat()
loadChat()
}
ignorePromise(f())
}

const clearCompletedKBFS = () => {
ignorePromise(
Promise.allSettled(
const f = async () => {
await Promise.allSettled(
[...get().kbfsJobs.values()].map(async job => {
if (job.phase === 'Done') {
return get().dispatch.cancelOrDismissKBFS(job.id)
}
})
)
)
loadKBFS()
loadKBFS()
}
ignorePromise(f())
}

const loadChat = () => {
Expand Down Expand Up @@ -273,19 +281,21 @@ export const useArchiveState = Z.createZustand<State>('archive', (set, get) => {
ignorePromise(f())
}

const startFSArchive = (path: string, outPath: string) => {
const f = async () => {
const actualPath = outPath || (isAndroid && fsCacheDir ? `${fsCacheDir}/kbfs-backup-${Date.now()}` : '')
await T.RPCGen.SimpleFSSimpleFSArchiveStartRpcPromise({
archiveJobStartPath: {
archiveJobStartPathType: T.RPCGen.ArchiveJobStartPathType.kbfs,
kbfs: pathToRPCPath(path).kbfs,
},
outputPath: actualPath,
overwriteZip: true,
})
}
ignorePromise(f())
const startArchiveSingle = (type: 'kbfs' | 'git', pathOrRepo: string, outPath: string) => {
const prefix = type === 'kbfs' ? 'kbfs-backup' : 'git-backup'
ignorePromise(
(async () => {
const actualPath = outPath || (isAndroid && fsCacheDir ? `${fsCacheDir}/${prefix}-${Date.now()}` : '')
await T.RPCGen.SimpleFSSimpleFSArchiveStartRpcPromise({
archiveJobStartPath:
type === 'kbfs'
? {archiveJobStartPathType: T.RPCGen.ArchiveJobStartPathType.kbfs, kbfs: pathToRPCPath(pathOrRepo).kbfs}
: {archiveJobStartPathType: T.RPCGen.ArchiveJobStartPathType.git, git: pathOrRepo},
outputPath: actualPath,
overwriteZip: true,
})
})()
)
}

const startFSArchiveAll = (outputDir: string) => {
Expand Down Expand Up @@ -314,22 +324,6 @@ export const useArchiveState = Z.createZustand<State>('archive', (set, get) => {
ignorePromise(f())
}

const startGitArchive = (gitRepo: string, outPath: string) => {
const f = async () => {
const actualPath =
outPath || (isAndroid && fsCacheDir ? `${fsCacheDir}/git-backup-${Date.now()}` : '')
await T.RPCGen.SimpleFSSimpleFSArchiveStartRpcPromise({
archiveJobStartPath: {
archiveJobStartPathType: T.RPCGen.ArchiveJobStartPathType.git,
git: gitRepo,
},
outputPath: actualPath,
overwriteZip: true,
})
}
ignorePromise(f())
}

const startGitArchiveAll = (outputDir: string) => {
set(s => {
s.archiveAllGitResponseWaiter.state = 'waiting'
Expand Down Expand Up @@ -441,10 +435,10 @@ export const useArchiveState = Z.createZustand<State>('archive', (set, get) => {
}
break
case 'kbfs':
target === '/keybase' ? startFSArchiveAll(outPath) : startFSArchive(target, outPath)
target === '/keybase' ? startFSArchiveAll(outPath) : startArchiveSingle('kbfs', target, outPath)
return
case 'git':
target === '.' ? startGitArchiveAll(outPath) : startGitArchive(target, outPath)
target === '.' ? startGitArchiveAll(outPath) : startArchiveSingle('git', target, outPath)
return
}
},
Expand Down
Loading