/** Live workspace signals used to advance the checklist without relying only on localStorage. */ export type ActivationWorkspaceEvidence = { hasEnabledFields?: boolean; hasSource?: boolean; hasMapping?: boolean; hasSyncedSample?: boolean; hasProcessed?: boolean; hasStoreConnect?: boolean; hasExport?: boolean; }; /** Minimal step shape for gating / workspace cursor (no i18n). */ export type ActivationStepRef = { id: string; optional?: boolean; feature?: string; }; /** Steps visible for the current plan (omit feature-gated steps the plan denies). */ export function visibleActivationSteps( steps: T[], can: (featureKey: string) => boolean = () => true ): T[] { return steps.filter((s) => !s.feature || can(s.feature)); } const evidenceForStep: Record< string, (evidence: ActivationWorkspaceEvidence) => boolean | undefined > = { "enable-fields": (e) => e.hasEnabledFields, "connect-source": (e) => e.hasSource, map: (e) => e.hasMapping, "sync-sample": (e) => e.hasSyncedSample, process: (e) => e.hasProcessed, "store-connect": (e) => e.hasStoreConnect, export: (e) => e.hasExport }; function stepEvidenceDone(step: ActivationStepRef, evidence: ActivationWorkspaceEvidence): boolean { const getter = evidenceForStep[step.id]; return getter ? Boolean(getter(evidence)) : false; } /** * Contiguous completed-step count from workspace state (stop at first incomplete). * Optional steps do not block later required evidence (e.g. export without a store). */ export function activationIndexFromWorkspace( evidence: ActivationWorkspaceEvidence | null | undefined, steps: ActivationStepRef[] ): number { if (!evidence) return 0; let count = 0; for (let i = 0; i < steps.length; i++) { const step = steps[i]; if (stepEvidenceDone(step, evidence)) { count += 1; continue; } if (step.optional) { const laterRequiredDone = steps .slice(i + 1) .some((s) => !s.optional && stepEvidenceDone(s, evidence)); if (laterRequiredDone) { count += 1; continue; } } break; } return Math.min(count, steps.length); }