diff --git a/.openhands_instructions b/.openhands_instructions new file mode 100644 index 0000000..b384155 --- /dev/null +++ b/.openhands_instructions @@ -0,0 +1,3 @@ +Read and follow `/AGENTS.md` — it is the single source of truth for project rules, coding standards, and design constraints. + +Do not commit documentation files, standalone test scripts, or debugging artifacts. Only commit production code and its corresponding spec files. diff --git a/src/app/shared/focus.service.spec.ts b/src/app/shared/focus.service.spec.ts index 6ae3dec..e7fb16b 100644 --- a/src/app/shared/focus.service.spec.ts +++ b/src/app/shared/focus.service.spec.ts @@ -56,6 +56,8 @@ describe('FocusService', () => { const ideasSignal = signal([makeIdea('id-1')]); const ticketsSignal = signal([makeTicket('tk-1')]); const pullRequestsSignal = signal([]); + const ticketsLoadingSignal = signal(false); + const pullRequestsLoadingSignal = signal(false); beforeEach(() => { localStorage.clear(); @@ -63,6 +65,8 @@ describe('FocusService', () => { ideasSignal.set([makeIdea('id-1')]); ticketsSignal.set([makeTicket('tk-1')]); pullRequestsSignal.set([]); + ticketsLoadingSignal.set(false); + pullRequestsLoadingSignal.set(false); TestBed.configureTestingModule({ providers: [ FocusService, @@ -70,7 +74,12 @@ describe('FocusService', () => { { provide: IdeaService, useValue: { ideas: ideasSignal } }, { provide: WorkspaceService, - useValue: { tickets: ticketsSignal, pullRequests: pullRequestsSignal }, + useValue: { + tickets: ticketsSignal, + pullRequests: pullRequestsSignal, + ticketsLoading: ticketsLoadingSignal, + pullRequestsLoading: pullRequestsLoadingSignal + }, }, ], }); @@ -133,4 +142,52 @@ describe('FocusService', () => { JSON.stringify({ id: 'td-1', type: 'todo' }), ); }); + + it('preserves focus when data is not loaded yet', () => { + // Set focus on a ticket + service.setFocus({ id: 'tk-1', type: 'ticket' }); + TestBed.tick(); + + // Verify focus is set + expect(service.focusTarget()).toEqual({ id: 'tk-1', type: 'ticket' }); + expect(service.focusedItem()).toEqual(makeTicket('tk-1')); + + // Simulate data being cleared (but loading is complete) + ticketsSignal.set([]); + TestBed.tick(); + + // Focus should be cleared because data is loaded and item doesn't exist + expect(service.focusTarget()).toBeNull(); + + // Now simulate the actual issue: focus loaded from storage but data not loaded yet + ticketsLoadingSignal.set(true); + + // Set focus again and clear data + service.setFocus({ id: 'tk-1', type: 'ticket' }); + ticketsSignal.set([]); + TestBed.tick(); + + // Focus should be preserved because data is still loading + expect(service.focusTarget()).toEqual({ id: 'tk-1', type: 'ticket' }); + expect(service.focusedItem()).toBeNull(); + + // Now mark data as loaded + ticketsLoadingSignal.set(false); + TestBed.tick(); + + // Focus should be cleared because data is loaded and item doesn't exist + expect(service.focusTarget()).toBeNull(); + }); + + it('clears focus when item no longer exists after data is loaded', () => { + service.setFocus({ id: 'tk-1', type: 'ticket' }); + TestBed.tick(); + + // Remove the ticket from data + ticketsSignal.set([]); + TestBed.tick(); + + // Focus should be cleared because data is loaded and item doesn't exist + expect(service.focusTarget()).toBeNull(); + }); }); diff --git a/src/app/shared/focus.service.ts b/src/app/shared/focus.service.ts index 4c04f0c..e468dd2 100644 --- a/src/app/shared/focus.service.ts +++ b/src/app/shared/focus.service.ts @@ -24,7 +24,7 @@ export class FocusService { effect(() => { const item = this.focusedItem(); const target = this.focusTarget(); - if (target && !item) { + if (target && !item && this.isDataLoadedForTarget(target)) { this.focusTarget.set(null); } }); @@ -64,6 +64,18 @@ export class FocusService { } } + private isDataLoadedForTarget(target: FocusTarget): boolean { + switch (target.type) { + case 'ticket': + return !this.data.ticketsLoading(); + case 'pr': + return !this.data.pullRequestsLoading(); + case 'todo': + case 'idea': + return true; + } + } + private loadFromStorage(): FocusTarget | null { try { const raw = localStorage.getItem(STORAGE_KEY);