Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
36 changes: 36 additions & 0 deletions frontend/taskdeck-web/src/tests/views/BoardView.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@ import { mount } from '@vue/test-utils'
import { reactive } from 'vue'
import BoardView from '../../views/BoardView.vue'

const mockSessionStore = reactive<{ userId: string | null; username: string | null }>({
userId: 'user-abc',
username: 'alice',
})

vi.mock('../../store/sessionStore', () => ({
useSessionStore: () => mockSessionStore,
}))

const routerMock = vi.hoisted(() => ({
push: vi.fn(),
}))
Expand Down Expand Up @@ -122,6 +131,8 @@ describe('BoardView', () => {
vi.clearAllMocks()
localStorage.clear()
routeMock.params.id = 'board-1'
mockSessionStore.userId = 'user-abc'
mockSessionStore.username = 'alice'
mockBoardStore.currentBoard = {
id: 'board-1',
name: 'Ops Board',
Expand Down Expand Up @@ -228,4 +239,29 @@ describe('BoardView', () => {

expect(addCardToggleMock).toHaveBeenCalledTimes(1)
})

it('seeds presence with the current user immediately on mount so the panel never flickers to empty', async () => {
// Verify setBoardPresenceMembers is called with the current user before
// fetchBoard and realtime.start resolve — no empty-state window (#523).
mountView()
await waitForUi()

const firstCall = mockBoardStore.setBoardPresenceMembers.mock.calls[0]
expect(firstCall).toBeDefined()
expect(firstCall[0]).toEqual([
{ userId: 'user-abc', displayName: 'alice', editingCardId: null },
])
})

it('seeds presence with empty array when no user session is active', async () => {
mockSessionStore.userId = null
mockSessionStore.username = null

mountView()
await waitForUi()

const firstCall = mockBoardStore.setBoardPresenceMembers.mock.calls[0]
expect(firstCall).toBeDefined()
expect(firstCall[0]).toEqual([])
})
})
29 changes: 25 additions & 4 deletions frontend/taskdeck-web/src/views/BoardView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { onBeforeUnmount, onMounted, ref, computed, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useBoardStore } from '../store/boardStore'
import { useSessionStore } from '../store/sessionStore'
import { useKeyboardShortcuts } from '../composables/useKeyboardShortcuts'
import { createBoardRealtimeController } from '../composables/useBoardRealtime'
import { useBoardDragDrop } from '../composables/useBoardDragDrop'
Expand All @@ -20,6 +21,7 @@ import { isClientOnboardingDemoBoardName } from '../utils/boardDemo'
const route = useRoute()
const router = useRouter()
const boardStore = useBoardStore()
const sessionStore = useSessionStore()

const newColumnName = ref('')
const showColumnForm = ref(false)
Expand All @@ -33,6 +35,17 @@ const presenceMembers = ref<BoardPresenceMember[]>([])

const boardLoadPerf = usePerformanceMark('board-load')

/**
* Build a presence entry for the current authenticated user so the panel
* shows immediately on mount, without waiting for the async SignalR
* BoardJoined push event. This eliminates the empty-state flicker.
*/
function currentUserPresenceSeed(): BoardPresenceMember[] {
const uid = sessionStore.userId
if (!uid) return []
return [{ userId: uid, displayName: sessionStore.username ?? null, editingCardId: null }]
}


const boardId = ref(route.params.id as string)
const realtime = createBoardRealtimeController({
Expand Down Expand Up @@ -81,11 +94,19 @@ const {
resetSelection,
} = useBoardKeyboardNav(sortedColumns)

function applyPresenceSeed() {
const seed = currentUserPresenceSeed()
presenceMembers.value = seed
boardStore.setBoardPresenceMembers(seed)
}

onMounted(async () => {
boardLoadPerf.start()
try {
presenceMembers.value = []
boardStore.setBoardPresenceMembers([])
// Seed presence with the current user immediately so the panel never
// shows "No active collaborators" while waiting for the SignalR
// BoardJoined push event (fixes #523 flicker).
applyPresenceSeed()
boardStore.setEditingCard(null)
await boardStore.fetchBoard(boardId.value)
await realtime.start(boardId.value)
Expand All @@ -106,8 +127,8 @@ watch(

boardId.value = nextBoardId
resetSelection()
presenceMembers.value = []
boardStore.setBoardPresenceMembers([])
// Seed with current user on board switch for the same reason as onMounted.
applyPresenceSeed()
boardStore.setEditingCard(null)

try {
Expand Down
Loading