The single tracking issue for the Hyveon desktop pivot. Authoritative scope, locked decisions, and migration plan live in the design spec .
Vision
Convert Hyveon from a Nest+React web app (operator-driven, Docker-shipped, hand-run terraform apply) into a single-binary cross-platform Electron desktop application that owns the full deploy lifecycle — config editing, plan/apply/destroy, log streaming — while keeping AWS as the v1 cloud and leaving abstraction seams for GCP/Azure later.
Milestones
Each milestone is independently releasable.
M1 — Runs as Electron
After M1: desktop app launches; every current feature works inside the Electron window via IPC. Operator still hand-runs terraform apply out of band.
M2 — Manages config
After M2: operator can add/edit/remove game-server entries from inside the app; tfvars round-trip to S3 with optimistic locking.
M3 — Runs terraform
After M3: fully self-managing. Fresh install on a clean machine reaches "dashboard ready" through the wizard; plan/apply/destroy entirely from the UI.
M4 — Polish & ship
After M4: shippable. NSIS / DMG / AppImage from CI; electron-updater wired (disabled until signing).
Companion housekeeping
Execution order
Epic-level dependency graph
A (#135) ──► B (#136) ──► F (#140)
│ │
│ ╰──► #82 ◄──╮
▼ │
G (#141) │ (#82 needs both B and #81)
│
C (#137) ──► #80 ──► #81 ──╯
│
╰──► D (#138) ──► E (#139)
#213 (standalone; land after rebrand, before D ships runs table)
First three to actually pick up
feat(workspaces): rename @gsd/* scope to @hyveon/* (incl. server → desktop-main) #142 — rename @gsd/* → @hyveon/* (Epic A entry-point; unblocks the entire pivot)
In parallel — define the four cloud-provider interfaces (Epic C entry-points):
feat(desktop-main): adopt nestjs-electron-ipc-transport in bootstrap path #151 — adopt nestjs-electron-ipc-transport (Epic B entry-point, after Epic A chassis)
Within-epic sequence
#135 — Epic A: Electron shell + build pipeline
feat(workspaces): rename @gsd/* scope to @hyveon/* (incl. server → desktop-main) #142 — rename @gsd/* → @hyveon/* (foundational)
In parallel after feat(workspaces): rename @gsd/* scope to @hyveon/* (incl. server → desktop-main) #142 :
feat(build): adopt electron-vite for dev + build pipeline #144 — adopt electron-vite (can run alongside)
feat(build): adopt electron-builder with three-target config #145 — adopt electron-builder (after feat(build): adopt electron-vite for dev + build pipeline #144 )
ci: matrix build for win/mac/linux artifacts #146 — CI matrix (after feat(build): adopt electron-builder with three-target config #145 )
#136 — Epic B: IPC migration of Nest controllers (starts after Epic A chassis is in)
feat(desktop-main): adopt nestjs-electron-ipc-transport in bootstrap path #151 — adopt nestjs-electron-ipc-transport
In parallel after feat(desktop-main): adopt nestjs-electron-ipc-transport in bootstrap path #151 :
feat(web): rewrite api.service.ts over window.gsd.* IPC bridge #159 — rewrite api.service.ts (after all controllers)
feat(web): replace EventSource log consumer with IPC subscription in LogsPage #160 — replace EventSource log consumer (after feat(desktop-main): convert LogsController to IPC streaming channel #154 )
In parallel after feat(web): rewrite api.service.ts over window.gsd.* IPC bridge #159 :
#137 — Epic C: Cloud provider abstraction + AWS impl (can run in parallel with Epic A/B; blocks everything in M2)
In parallel:
feat(workspaces): scaffold @hyveon/cloud-aws package #168 — scaffold @hyveon/cloud-aws (after interfaces)
In parallel after feat(workspaces): scaffold @hyveon/cloud-aws package #168 :
feat(desktop-main): CloudProviderModule with DI bindings #183 — CloudProviderModule (after all impls)
refactor(terraform): split terraform/ into terraform/aws/ subdirectory #185 — split terraform/ into terraform/aws/
feat(terraform): top-level composer (main.tf + variables.tf) #187 — top-level composer (after refactor(terraform): split terraform/ into terraform/aws/ subdirectory #185 )
chore(lint): ban @aws-sdk/* imports outside @hyveon/cloud-aws and lambda packages #195 — ESLint rule banning @aws-sdk/* imports (anytime after feat(workspaces): scaffold @hyveon/cloud-aws package #168 )
#80 — Remote tfvars storage (after Epic C delivers RemoteFileStore)
feat(terraform): bootstrap module — versioned S3 bucket for tfvars #84 — versioned S3 bucket terraform module
feat(iam): GameServerDeployAll entries for the tfvars bucket #86 — IAM entries for the bucket
feat(desktop-main): bootstrap the tfvars bucket on first run #85 — bootstrap the bucket on first run
feat(desktop-main): RemoteTfvarsStore service (pull/push/diff/lock) #87 — RemoteTfvarsStore service
feat(desktop-main): wire RemoteTfvarsStore into bootstrap path #88 — wire into bootstrap path
feat(desktop-main): migration path from pre-pivot local tfvars #89 — migration path from pre-pivot local tfvars
docs: document S3 tfvars workflow #90 — docs
#81 — TfvarsService reads from RemoteFileStore (after #80 )
feat(desktop-main): TfvarsService — parse and cache S3-backed tfvars #91 — TfvarsService parse + cache
feat(desktop-main): gsd.games.list IPC — declared game_servers merged with live tfstate #92 — gsd.games.list IPC (declared + live merge)
feat(web): Games settings page (read-only) #93 — Games settings page (read-only)
feat(desktop-main): drift detection — declared-vs-live mismatches #94 — drift detection
test(desktop-main): TfvarsService unit + integration coverage #95 — unit + integration tests
#82 — Add/edit/remove games via desktop UI (after #81 AND #136 )
feat(shared): tfvars schema validation (Zod) #97 — Zod schema validation in @hyveon/shared
feat(desktop-main): write game_servers to S3 tfvars with optimistic locking #96 — write game_servers to S3 with IfMatch
feat(desktop-main): gsd.games.create/update/delete IPC handlers #98 — CRUD IPC handlers
In parallel:
feat(web): pending-changes banner #101 — pending-changes banner
feat(desktop-main): audit log in DynamoDB #102 — audit log in DynamoDB
#138 — Epic D: Local terraform orchestration (after Epic C and #81 )
feat(desktop-main): TerraformService scaffolding + binary detection #169 — TerraformService scaffolding + binary detection
feat(desktop-main): TerraformService.init() with backend-config args #171 — init()
feat(desktop-main): TerraformService.plan() producing tfplan artifact #173 — plan()
feat(desktop-main): TerraformService.apply() with stale-plan guard #175 — apply() with stale-plan guard (after feat(desktop-main): TerraformService.plan() producing tfplan artifact #173 )
feat(desktop-main): TerraformService.destroy() with explicit confirmation #177 — destroy() with explicit confirmation
feat(desktop-main): TerraformService.output() exposing tf outputs #178 — output()
feat(terraform): DynamoDB runs table for terraform-run records #105 — DynamoDB runs table (inherited from closed epic: web-app-driven terraform plan/apply pipeline #83 )
feat(desktop-main): run-record persistence to DynamoDB with S3 log offload #179 — run-record persistence (after feat(terraform): DynamoDB runs table for terraform-run records #105 )
In parallel:
UI work in parallel (inherited from epic: web-app-driven terraform plan/apply pipeline #83 ) :
#139 — Epic E: First-run wizard + credentials UX (after Epic D — wizard's final step calls TerraformService.init)
Foundational services in parallel:
feat(web): wizard step — install prerequisites #184 — wizard step install prerequisites (after feat(desktop-main): prerequisite-detection service (terraform, aws on PATH) #182 )
feat(web): wizard step — pick cloud (v1: AWS only) #186 — wizard step pick cloud
feat(web): wizard step — pick or paste credentials #192 — wizard step pick or paste credentials (after feat(desktop-main): AwsProfileService — list and read ~/.aws/credentials #189 , feat(desktop-main): safeStorage paste-flow for credentials #197 )
SDK bootstraps in parallel:
feat(desktop-main): IAM SimulatePrincipalPolicy check against GameServerDeployAll #208 — IAM SimulatePrincipalPolicy check
feat(web): wizard step — terraform init with live log #210 — wizard step terraform init (after feat(desktop-main): SDK bootstrap of S3 state bucket #200 , feat(desktop-main): SDK bootstrap of DynamoDB lock table #203 , feat(desktop-main): SDK bootstrap of S3 tfvars bucket #205 , and feat(desktop-main): TerraformService.init() with backend-config args #171 from Epic D)
feat(web): Reconfigure entry point in Settings #211 — Reconfigure entry point in Settings
#140 — Epic F: Test migration to Playwright Electron (after Epic B)
feat(test): adopt _electron.launch() Playwright API in e2e config #188 — adopt _electron.launch() Playwright API
feat(test): test-only window.gsd.__test.mock() injected by preload #198 — test-only window.gsd.__test.mock() injection
Spec migrations in parallel (after feat(test): adopt _electron.launch() Playwright API in e2e config #188 , feat(test): test-only window.gsd.__test.mock() injected by preload #198 ):
feat(test): recast integration tier — main-process + IPC + AWS-mock #199 — recast integration tier (main-process + IPC + AWS-mock)
feat(test): app/test/fake-terraform.mjs scripted-output stand-in #201 — fake-terraform.mjs scripted stand-in
test(integration): orchestrator coverage using fake-terraform.mjs #204 — orchestrator integration coverage (after feat(test): recast integration tier — main-process + IPC + AWS-mock #199 and feat(test): app/test/fake-terraform.mjs scripted-output stand-in #201 )
#141 — Epic G: Distribution + auto-update scaffolding (rides alongside M2/M3, finalizes at M4)
ci: tag-triggered release workflow publishing artifacts to GitHub Releases #202 — tag-triggered release workflow (after ci: matrix build for win/mac/linux artifacts #146 — CI matrix in Epic A)
feat(desktop-main): electron-updater wired with disabled feature flag #206 — electron-updater wired with disabled feature flag
docs: unsigned-MVP install instructions per OS #207 — unsigned-MVP install docs per OS
docs: code-signing roadmap (Apple Developer, Azure Trusted Signing) #209 — code-signing roadmap
#213 — terraform project_name migration (standalone follow-up)
Out of scope
GCP / Azure cloud provider implementations. Epic C ships the four interface seams; second-cloud impls are deliberately deferred.
Code signing and active auto-update. Epic G ships the scaffolding; flipping the feature flag and paying for Apple Developer / Azure Trusted Signing is a follow-up capability.
Multi-user desktop access. Desktop is single-operator by design; Discord-side multi-user permissions stay fully intact.
Closing the Docker-compose / Nest-on-port-3001 deployment story. That goes away on M1; not tracked here separately.
Acceptance
A non-technical operator can download a single Hyveon installer (.exe / .dmg / .AppImage) on Windows / macOS / Linux, run a first-time wizard, and have a working AWS deployment without ever touching the terraform or AWS CLIs manually .
The desktop app is the only operator surface — no setup.sh, no Docker, no terraform apply from a terminal.
Discord-side gameplay (slash commands, multi-user permissions, autocomplete) is unchanged from the user's perspective.
The CloudProvider abstraction in @hyveon/shared has exactly one v1 implementation (AwsCloudProvider) and no AWS types leak through the interface signatures.
Status
Track milestone state via project board #2 filtered by this issue.
Vision
Convert Hyveon from a Nest+React web app (operator-driven, Docker-shipped, hand-run
terraform apply) into a single-binary cross-platform Electron desktop application that owns the full deploy lifecycle — config editing, plan/apply/destroy, log streaming — while keeping AWS as the v1 cloud and leaving abstraction seams for GCP/Azure later.Milestones
Each milestone is independently releasable.
M1 — Runs as Electron
After M1: desktop app launches; every current feature works inside the Electron window via IPC. Operator still hand-runs
terraform applyout of band.M2 — Manages config
RemoteFileStoreinterface + AWS S3 implTfvarsServicereads fromRemoteFileStorein desktop-mainAfter M2: operator can add/edit/remove game-server entries from inside the app; tfvars round-trip to S3 with optimistic locking.
M3 — Runs terraform
After M3: fully self-managing. Fresh install on a clean machine reaches "dashboard ready" through the wizard; plan/apply/destroy entirely from the UI.
M4 — Polish & ship
After M4: shippable. NSIS / DMG / AppImage from CI;
electron-updaterwired (disabled until signing).Companion housekeeping
project_namefrom"game-servers-poc"to"hyveon"— should land before epic: local terraform orchestration #138 so the runs table and run-log S3 keys are created under the new name.Execution order
Epic-level dependency graph
First three to actually pick up
@gsd/*→@hyveon/*(Epic A entry-point; unblocks the entire pivot)nestjs-electron-ipc-transport(Epic B entry-point, after Epic A chassis)Within-epic sequence
#135 — Epic A: Electron shell + build pipeline
@gsd/*→@hyveon/*(foundational)process.resourcesPath#136 — Epic B: IPC migration of Nest controllers (starts after Epic A chassis is in)
nestjs-electron-ipc-transportapi.service.ts(after all controllers)EventSourcelog consumer (after feat(desktop-main): convert LogsController to IPC streaming channel #154)ApiTokenGuardApiTokenModalembed-tfstate.mjs#137 — Epic C: Cloud provider abstraction + AWS impl (can run in parallel with Epic A/B; blocks everything in M2)
@hyveon/cloud-aws(after interfaces)terraform/intoterraform/aws/@aws-sdk/*imports (anytime after feat(workspaces): scaffold @hyveon/cloud-aws package #168)#80 — Remote tfvars storage (after Epic C delivers
RemoteFileStore)RemoteTfvarsStoreservice#81 —
TfvarsServicereads fromRemoteFileStore(after #80)TfvarsServiceparse + cachegsd.games.listIPC (declared + live merge)#82 — Add/edit/remove games via desktop UI (after #81 AND #136)
@hyveon/sharedgame_serversto S3 withIfMatch#138 — Epic D: Local terraform orchestration (after Epic C and #81)
TerraformServicescaffolding + binary detectioninit()plan()apply()with stale-plan guard (after feat(desktop-main): TerraformService.plan() producing tfplan artifact #173)destroy()with explicit confirmationoutput()#139 — Epic E: First-run wizard + credentials UX (after Epic D — wizard's final step calls
TerraformService.init)SimulatePrincipalPolicycheck#140 — Epic F: Test migration to Playwright Electron (after Epic B)
_electron.launch()Playwright APIwindow.gsd.__test.mock()injectionfake-terraform.mjsscripted stand-in#141 — Epic G: Distribution + auto-update scaffolding (rides alongside M2/M3, finalizes at M4)
electron-updaterwired with disabled feature flag#213 — terraform
project_namemigration (standalone follow-up)Out of scope
Acceptance
.exe/.dmg/.AppImage) on Windows / macOS / Linux, run a first-time wizard, and have a working AWS deployment without ever touching the terraform or AWS CLIs manually.setup.sh, no Docker, noterraform applyfrom a terminal.CloudProviderabstraction in@hyveon/sharedhas exactly one v1 implementation (AwsCloudProvider) and no AWS types leak through the interface signatures.Status
Track milestone state via project board #2 filtered by this issue.