Skip to content

Workflow Overview

A workflow is a named, typed unit of work — for example "course.load", "repo.create", or "connection.verifyLmsDraft". Workflows are the central execution abstraction of repo-edu: every user-facing operation that involves I/O, progress reporting, or error handling runs as a workflow.

repo-edu ships three delivery surfaces: a desktop Electron app, a CLI, and a browser-based demo. Each surface has different transport mechanics (IPC, in-process, in-browser), but the underlying business logic is identical. Workflows decouple what the application does from how each surface delivers it.

A single workflow definition in @repo-edu/application-contract is enough for all three surfaces to execute the same operation with full type safety — typed input, typed progress events, typed output, and typed result.

Every workflow has a unique string identifier following the pattern domain.verb, for example:

  • "course.load", "course.save", "course.delete"
  • "connection.verifyLmsDraft", "connection.verifyGitDraft"
  • "repo.create", "repo.clone", "repo.update"

The WorkflowId type is the union of all valid IDs, derived from the keys of WorkflowPayloads.

The WorkflowPayloads type map in packages/application-contract/src/index.ts is the single source of truth. It maps each workflow ID to four typed channels:

type WorkflowPayloads = {
"course.load": {
input: { courseId: string }
progress: MilestoneProgress
output: DiagnosticOutput
result: PersistedCourse
}
// ... every other workflow
}

See Payload Channels for what each channel means.

WorkflowClient is the interface that callers use to run workflows. It has a single generic method:

type WorkflowClient = {
run<TId extends WorkflowId>(
workflowId: TId,
input: WorkflowInput<TId>,
options?: WorkflowCallOptions<WorkflowProgress<TId>, WorkflowOutput<TId>>,
): Promise<WorkflowResult<TId>>
}

Callers never know (or care) which transport delivers the execution. The React renderer calls client.run("course.load", { courseId }) the same way regardless of whether the client is backed by tRPC-electron IPC, an in-process handler, or a browser mock.

On the other side, each workflow has a handler — a function that receives the typed input and optional callbacks, and returns the typed result:

type WorkflowHandler<TId extends WorkflowId> = (
input: WorkflowInput<TId>,
options?: WorkflowCallOptions<WorkflowProgress<TId>, WorkflowOutput<TId>>,
) => Promise<WorkflowResult<TId>>

Handlers live in packages/application/src/ and are grouped by domain (course, connection, roster, etc.). They orchestrate calls to ports (HTTP, Git, filesystem) and domain logic.

Workflows are organized into domain groups:

  • course — list, load, save, delete courses
  • settings — load and save application settings
  • connection — verify LMS and Git connection drafts, list LMS courses
  • roster — import rosters from file or LMS, export members
  • groupSet — fetch, connect, sync, preview import, and export group sets
  • gitUsernames — import Git usernames for roster members
  • validation — validate roster and assignment configurations
  • repo — create, clone, and update repositories
  • userFile — inspect file selections and preview exports

Not all workflows are available on all surfaces. The workflow catalog declares which surfaces support each workflow.

ConceptLocation
Type definitions and catalogpackages/application-contract/src/index.ts
Workflow handlerspackages/application/src/*-workflows.ts
Desktop transport (tRPC)apps/desktop/src/trpc.ts
Desktop client (renderer)apps/desktop/src/workflow-client.ts
CLI transport (in-process)apps/cli/src/workflow-runtime.ts
Docs transport (in-browser)apps/docs/src/demo-runtime.ts
React contextpackages/renderer-app/src/contexts/workflow-client.tsx