fix(tab-guard): name the company the other tab switched to (#2328)
The cross-tab guard dialog said "another tab switched the active company" without saying to which one, so the two exits read as "your company" versus "the new one". Resolve the observed company id against the memberships the shell already ships to the client (switcher list plus the foreign-host signpost list; no request at the moment the tab is told to stop) and say "en annan flik har bytt till Demo AB" and "Ladda om som Demo AB". Unknown ids keep the unnamed wording. Founder re-confirmed the blocking two-exit design (WL-09) today after a forensic pass on a real firing; this is copy only, no behaviour change. Claude-Session: https://claude.ai/code/session_01CQG9jNyxM7mwMUHWBrFUxY Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Jakob Wennberg
Claude Fable 5.1
parent
473b1fd2eb
commit
b0b995c77c
@@ -8,6 +8,7 @@ import {
|
||||
markCompanySwitchInFlight,
|
||||
markSelfSwitchTarget,
|
||||
requestHasNextActionHeader,
|
||||
resolveObservedCompanyName,
|
||||
shouldBlockMutation,
|
||||
} from '../tab-guard'
|
||||
|
||||
@@ -244,3 +245,25 @@ describe('guardBrowserWrite', () => {
|
||||
expect(guardBrowserWrite()).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('resolveObservedCompanyName', () => {
|
||||
const companies = [
|
||||
{ company: { id: 'c-arcim', name: 'Arcim Technology AB' } },
|
||||
{ company: { id: 'c-demo', name: 'Demo AB' } },
|
||||
]
|
||||
const foreign = [{ id: 'c-byra', name: 'Klientbolaget AB' }]
|
||||
|
||||
it('names a company from the switcher list', () => {
|
||||
expect(resolveObservedCompanyName('c-demo', companies, foreign)).toBe('Demo AB')
|
||||
})
|
||||
|
||||
it('names a company homed on another host from the signpost list', () => {
|
||||
expect(resolveObservedCompanyName('c-byra', companies, foreign)).toBe('Klientbolaget AB')
|
||||
})
|
||||
|
||||
it('is null for unknown ids and for no observation, so the dialog keeps its unnamed wording', () => {
|
||||
expect(resolveObservedCompanyName('c-elsewhere', companies, foreign)).toBeNull()
|
||||
expect(resolveObservedCompanyName(null, companies, foreign)).toBeNull()
|
||||
expect(resolveObservedCompanyName(undefined, companies)).toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -161,6 +161,26 @@ export function isTabMismatch(
|
||||
return observedCompanyId !== tabCompanyId
|
||||
}
|
||||
|
||||
/**
|
||||
* Name of the company another tab switched to, for the guard dialog. Looks
|
||||
* through the memberships the shell already handed the client (the switcher
|
||||
* list and the foreign-host signpost list), so no request is needed at the
|
||||
* moment the tab is being told to stop. Null when the id is not among them
|
||||
* (a company this login cannot see on this host): the dialog then falls back
|
||||
* to its unnamed wording rather than guessing.
|
||||
*/
|
||||
export function resolveObservedCompanyName(
|
||||
observedCompanyId: string | null | undefined,
|
||||
companies: readonly { company: { id: string; name: string } }[],
|
||||
foreignCompanies: readonly { id: string; name: string }[] = [],
|
||||
): string | null {
|
||||
if (!observedCompanyId) return null
|
||||
const local = companies.find((entry) => entry.company.id === observedCompanyId)
|
||||
if (local?.company.name) return local.company.name
|
||||
const foreign = foreignCompanies.find((entry) => entry.id === observedCompanyId)
|
||||
return foreign?.name || null
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a fetch about to leave this tab must be blocked. Guards mutating
|
||||
* same-origin requests on two shapes:
|
||||
|
||||
Reference in New Issue
Block a user