singularity-forge/web/lib/workflow-actions.ts
Mikael Hugo 22cbd83675 fix: update test snapshots for queryInstruction and complete /sf prefix Phase 2 deprecation
- Fix memory-embeddings-llm-gateway tests: add queryInstruction field to
  expected config objects after loadGatewayConfigFromEnv was updated to
  return it
- Add STYLEGUIDE.md: SF code standards adapted from ace-coder patterns
  (purpose doctrine, principles, anti-patterns STY001-012, thresholds,
  naming, patterns, documentation sections)
- Phase 2 /sf prefix removal: update all web components, browser dispatch,
  and tests to use direct commands (/autonomous, /stop, /next, /discuss,
  /init, /new-milestone) instead of /sf-prefixed forms
  - workflow-actions.ts: all command strings updated
  - chat-mode.tsx: SF_ACTIONS array updated
  - project-welcome.tsx: primaryCommand values updated
  - command-surface.tsx: fallback display updated
  - remaining-command-panels.tsx: usage examples updated
  - browser-slash-command-dispatch.ts: add stop/new-milestone/init to
    SF_PASSTHROUGH_COMMANDS so they route correctly to the extension
  - recovery-diagnostics-service.ts: suggestion commands updated
  - welcome-screen.ts: hint text updated
  - All affected tests updated to match new command strings

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-09 00:17:47 +02:00

148 lines
4.2 KiB
TypeScript

/**
* Pure derivation of the primary workflow action based on workspace state.
* No React dependencies — fully testable with plain imports.
*/
export interface WorkflowActionInput {
phase: string;
autoActive: boolean;
autoPaused: boolean;
onboardingLocked: boolean;
commandInFlight: string | null;
bootStatus: string;
hasMilestones: boolean;
/** When set, suppresses the action bar if the welcome screen is handling initialization. */
projectDetectionKind?: string | null;
}
export interface WorkflowAction {
label: string;
command: string;
variant: "default" | "destructive";
}
export interface WorkflowActionResult {
primary: WorkflowAction | null;
secondaries: { label: string; command: string }[];
disabled: boolean;
disabledReason?: string;
/** When true, the action represents the all-milestones-complete "New Milestone" state. */
isNewMilestone: boolean;
}
export function deriveWorkflowAction(
input: WorkflowActionInput,
): WorkflowActionResult {
const {
phase,
autoActive,
autoPaused,
onboardingLocked,
commandInFlight,
bootStatus,
hasMilestones,
projectDetectionKind,
} = input;
// When the project welcome screen is active, it handles the initialization CTA.
// Suppress the action bar to avoid duplicate/confusing buttons.
if (
projectDetectionKind &&
projectDetectionKind !== "active-sf" &&
projectDetectionKind !== "empty-sf"
) {
return {
primary: null,
secondaries: [],
disabled: true,
disabledReason: "Project setup pending",
isNewMilestone: false,
};
}
// Determine disabled state and reason
let disabled = false;
let disabledReason: string | undefined;
if (commandInFlight !== null) {
disabled = true;
disabledReason = "Command in progress";
} else if (bootStatus !== "ready") {
disabled = true;
disabledReason = "Workspace not ready";
} else if (onboardingLocked) {
disabled = true;
disabledReason = "Setup required";
}
// Derive primary action
let primary: WorkflowAction | null = null;
const secondaries: { label: string; command: string }[] = [];
let isNewMilestone = false;
if (autoActive && !autoPaused) {
primary = {
label: "Stop Autonomous",
command: "/stop",
variant: "destructive",
};
} else if (autoPaused) {
primary = {
label: "Resume Autonomous",
command: "/autonomous",
variant: "default",
};
} else {
// Auto is not active
if (phase === "complete") {
// All milestones done — surface a distinct "New Milestone" action
primary = { label: "New Milestone", command: "/new-milestone", variant: "default" };
isNewMilestone = true;
} else if (phase === "planning") {
primary = { label: "Plan", command: "/discuss", variant: "default" };
} else if (phase === "executing" || phase === "summarizing") {
primary = {
label: "Start Autonomous",
command: "/autonomous",
variant: "default",
};
} else if (phase === "pre-planning" && !hasMilestones) {
primary = {
label: "Initialize Project",
command: "/init",
variant: "default",
};
} else if (phase === "blocked") {
primary = { label: "Blocked", command: "/discuss", variant: "default" };
disabled = true;
disabledReason = "Project is blocked — check blockers";
} else if (phase === "paused") {
primary = {
label: "Resume",
command: "/autonomous",
variant: "default",
};
} else if (phase === "validating-milestone") {
primary = { label: "Validate", command: "/discuss", variant: "default" };
} else if (phase === "completing-milestone") {
primary = {
label: "Complete Milestone",
command: "/discuss",
variant: "default",
};
} else if (phase === "needs-discussion") {
primary = { label: "Discuss", command: "/discuss", variant: "default" };
} else if (phase === "replanning-slice") {
primary = { label: "Replan", command: "/discuss", variant: "default" };
} else {
primary = { label: "Continue", command: "/discuss", variant: "default" };
}
// Add "Step" secondary when auto is not active (not for new milestone — no step concept there)
if (primary.command !== "/next" && !isNewMilestone) {
secondaries.push({ label: "Step", command: "/next" });
}
}
return { primary, secondaries, disabled, disabledReason, isNewMilestone };
}