diff --git a/src/app/shared/feature-flags.ts b/src/app/shared/feature-flags.ts index 1c04de8..7c33acb 100644 --- a/src/app/shared/feature-flags.ts +++ b/src/app/shared/feature-flags.ts @@ -10,3 +10,14 @@ export const SWIMLANES_ENABLED = false; * aspirational. See docs/project-memory.md before changing this flag. */ export const PROJECTS_ENABLED = true; + +/** + * Finalizing a flow makes it permanently read-only and is irreversible, and it is not part of the + * current workflow, so the controls that create that state are hidden. + * + * Only the *controls* are gated - the Finalized toggle and the Finalized filter. The indicators + * that explain the state stay visible (the badge on a flow row, the disabled delete, the read-only + * editor), because rows finalized earlier still exist and hiding their explanation would make the + * app inexplicable. See docs/project-memory.md before changing this flag. + */ +export const FLOW_FINALIZATION_ENABLED = false; diff --git a/src/app/shared/flows-list/flows-list.html b/src/app/shared/flows-list/flows-list.html index a67b83e..5ca0db3 100644 --- a/src/app/shared/flows-list/flows-list.html +++ b/src/app/shared/flows-list/flows-list.html @@ -27,7 +27,9 @@ All Public Private + @if (finalizationEnabled) { Finalized + }
diff --git a/src/app/shared/flows-list/flows-list.spec.ts b/src/app/shared/flows-list/flows-list.spec.ts index f175ff1..4055a01 100644 --- a/src/app/shared/flows-list/flows-list.spec.ts +++ b/src/app/shared/flows-list/flows-list.spec.ts @@ -11,6 +11,7 @@ import { of, throwError } from 'rxjs'; import { ListState } from '@stores/list-state'; import { vi } from 'vitest'; +import { FLOW_FINALIZATION_ENABLED } from '@shared/feature-flags'; import { FlowsList } from './flows-list'; function makeFlow(id: string, name: string, projectId?: string): Flow { @@ -258,4 +259,32 @@ describe('FlowsList', () => { expect(component.activeFilterCount()).toBe(2); }); + + it('offers the Finalized filter only while finalization is enabled', async () => { + const fixture = await build([makeFlow('1', 'Alpha')], []); + fixture.componentInstance.toggleFilters(); + fixture.detectChanges(); + + const values = Array.from( + fixture.nativeElement.querySelectorAll('mat-button-toggle') + ).map((toggle: any) => toggle.textContent.trim()); + + expect(values).toContain('All'); + expect(values.includes('Finalized')).toBe(FLOW_FINALIZATION_ENABLED); + }); + + it('does not keep narrowing the list with a filter whose control is hidden', async () => { + // A persisted Finalized filter would otherwise hide flows with nothing on screen to clear it. + const fixture = await build([makeFlow('1', 'Alpha')], []); + const view = fixture.componentInstance.view; + view.filter = 'FINALIZED'; + + TestBed.resetTestingModule(); + const reopened = await build([makeFlow('1', 'Alpha')], []); + + if (!FLOW_FINALIZATION_ENABLED) { + expect(reopened.componentInstance.filter()).toBe('all'); + expect(reopened.componentInstance.orderedFlows()).toHaveLength(1); + } + }); }); diff --git a/src/app/shared/flows-list/flows-list.ts b/src/app/shared/flows-list/flows-list.ts index 8a940f9..55a1256 100644 --- a/src/app/shared/flows-list/flows-list.ts +++ b/src/app/shared/flows-list/flows-list.ts @@ -20,7 +20,7 @@ import { NotificationService } from '@services/notifications/notification'; import { TaskExecutionsService } from '@services/task-executions/task-executions'; import { ProjectsService } from '@services/projects/projects'; import { EditorStateHolder } from '@stores/flow-editor'; -import { PROJECTS_ENABLED } from '@shared/feature-flags'; +import { FLOW_FINALIZATION_ENABLED, PROJECTS_ENABLED } from '@shared/feature-flags'; import { OrderEvent, OrderField, Ordering, orderDirType } from "@shared/ordering/ordering"; import { ListStateViewHolder, OrderViewState } from '@utilities/list-state-holder'; import { FlowsGroup } from './flows-group/flows-group'; @@ -63,6 +63,7 @@ export class FlowsList extends ListStateViewHolder { private editorState = inject(EditorStateHolder); readonly projectsEnabled = PROJECTS_ENABLED; + readonly finalizationEnabled = FLOW_FINALIZATION_ENABLED; readonly ungroupedKey = UNGROUPED_PROJECT_KEY; readonly projects = this.projectsService.projects; @@ -98,6 +99,14 @@ export class FlowsList extends ListStateViewHolder { this.filtersOpen.update((open) => !open); } + /** + * A filter whose control is hidden would keep narrowing the list with no way to see or clear it, + * so a persisted Finalized filter falls back to showing everything. + */ + private usableFilter(filter: FlowFilter): FlowFilter { + return filter === 'FINALIZED' && !FLOW_FINALIZATION_ENABLED ? 'all' : (filter || 'all'); + } + constructor() { super('flowsList', {defaultOrder: { orderBy: 'name', orderDir: 'asc' } as OrderViewState, defaultFilter: 'all'}); effect(() => { @@ -125,7 +134,7 @@ export class FlowsList extends ListStateViewHolder { this.flows = existingState.list; this.loading.set(false); if (existingState.filter) - this.filter.set(existingState.filter as FlowFilter || 'all'); + this.filter.set(this.usableFilter(existingState.filter as FlowFilter)); return; } diff --git a/src/app/shared/title-toolbar/title-toolbar.html b/src/app/shared/title-toolbar/title-toolbar.html index 7b6ed4d..2ca8c15 100644 --- a/src/app/shared/title-toolbar/title-toolbar.html +++ b/src/app/shared/title-toolbar/title-toolbar.html @@ -95,6 +95,7 @@ Published + @if (finalizationEnabled || flow()!.finalized) { Finalized + }
}