roast findings: arm ordering, composer deps, follow-all fallthrough, createdAt clobber
Adversarial review (roast) of the branch, 9 path-scoped passes: - cross-tab rebuild armed the new session's hooks before the deferred bundle dispatch, so an 'expired' event fired in that async window would be dropped by the reducer's bundle-identity check; arm now happens in the same callback as the dispatch, and the dispatched account is re-read from the live session (mirrors snapshot-after- prep). - Composer onPressPublish's dep array omitted appviewClient and resolveClients (stale-client hazard after cross-tab rebuild). - follow-all catch blocks (onboarding starter-pack card, starter pack screen) showed the error toast but fell through to dereference the undefined followUris map, throwing and showing a bogus success toast. - contacts createProfileRecord unconditionally overwrote the profile's createdAt on every run; now preserved when already set, matching StepFinished. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -347,7 +347,9 @@ async function createProfileRecord(
|
|||||||
|
|
||||||
next.displayName = ''
|
next.displayName = ''
|
||||||
|
|
||||||
next.createdAt = toDatetimeString(new Date())
|
if (!next.createdAt) {
|
||||||
|
next.createdAt = toDatetimeString(new Date())
|
||||||
|
}
|
||||||
return next
|
return next
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ export function StarterPackCard({
|
|||||||
type: 'error',
|
type: 'error',
|
||||||
})
|
})
|
||||||
logger.error('Failed to follow all accounts', {safeMessage: e})
|
logger.error('Failed to follow all accounts', {safeMessage: e})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsFollowingAll(true)
|
setIsFollowingAll(true)
|
||||||
|
|||||||
@@ -385,6 +385,7 @@ function Header({
|
|||||||
type: 'error',
|
type: 'error',
|
||||||
})
|
})
|
||||||
logger.error('Failed to follow all accounts', {safeMessage: e})
|
logger.error('Failed to follow all accounts', {safeMessage: e})
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsProcessing(false)
|
setIsProcessing(false)
|
||||||
|
|||||||
@@ -452,7 +452,6 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
|||||||
)
|
)
|
||||||
newBundle = buildBundle(newSession)
|
newBundle = buildBundle(newSession)
|
||||||
registerBundleKillSwitch(newBundle, hooks.kill)
|
registerBundleKillSwitch(newBundle, hooks.kill)
|
||||||
hooks.arm()
|
|
||||||
/*
|
/*
|
||||||
* Reapply this account's subscribed labelers to the freshly built
|
* Reapply this account's subscribed labelers to the freshly built
|
||||||
* appview client - buildBundle starts with an empty per-instance
|
* appview client - buildBundle starts with an empty per-instance
|
||||||
@@ -465,6 +464,15 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
|||||||
* preserving this branch's no-network intent), and the OLD bundle's
|
* preserving this branch's no-network intent), and the OLD bundle's
|
||||||
* access token stays valid throughout the deferral, so nothing
|
* access token stays valid throughout the deferral, so nothing
|
||||||
* regresses by waiting.
|
* regresses by waiting.
|
||||||
|
*
|
||||||
|
* arm() happens inside the same callback as the dispatch (not
|
||||||
|
* before the deferral) so there is no async window where the new
|
||||||
|
* session is armed but the reducer still holds the old bundle -
|
||||||
|
* an 'expired' event fired in such a window would be dropped by
|
||||||
|
* the reducer's bundle-identity check. Mirroring the factories'
|
||||||
|
* snapshot-after-prep, the dispatched account is re-read from the
|
||||||
|
* live session, so a token refresh during the (unarmed) deferral
|
||||||
|
* still persists fresh tokens.
|
||||||
*/
|
*/
|
||||||
void configureModerationForAccount(newBundle, syncedAccount)
|
void configureModerationForAccount(newBundle, syncedAccount)
|
||||||
.catch(() => {})
|
.catch(() => {})
|
||||||
@@ -478,10 +486,17 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
|
|||||||
: undefined,
|
: undefined,
|
||||||
nextSession: newBundle.session.session,
|
nextSession: newBundle.session.session,
|
||||||
})
|
})
|
||||||
|
const newAccount = newBundle.session.destroyed
|
||||||
|
? syncedAccount
|
||||||
|
: (sessionDataToSessionAccount(
|
||||||
|
newBundle.session.session,
|
||||||
|
newBundle.session.session.service,
|
||||||
|
) ?? syncedAccount)
|
||||||
|
hooks.arm()
|
||||||
store.dispatch({
|
store.dispatch({
|
||||||
type: 'replaced-current-bundle',
|
type: 'replaced-current-bundle',
|
||||||
newAgent: newBundle,
|
newAgent: newBundle,
|
||||||
newAccount: syncedAccount,
|
newAccount,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1271,6 +1271,8 @@ export const ComposePost = ({
|
|||||||
l,
|
l,
|
||||||
ax,
|
ax,
|
||||||
pdsClient,
|
pdsClient,
|
||||||
|
appviewClient,
|
||||||
|
resolveClients,
|
||||||
canPost,
|
canPost,
|
||||||
isPublishing,
|
isPublishing,
|
||||||
currentLanguages,
|
currentLanguages,
|
||||||
|
|||||||
Reference in New Issue
Block a user