Thanks for your interest in improving FlashForgeUI! This guide covers how the project is organized, how to set up a development environment, the standards we hold code to, and how changes travel from a pull request to a stable release.
If you are reporting a bug or requesting a feature rather than writing code, please open an issue. Include your printer model, firmware version, operating system, and FlashForgeUI version.
- Work from
alpha, never frommain. Branch fromalphaand open your pull request againstalpha. - Run
pnpm type-check,pnpm lint,pnpm build, andpnpm testbefore opening the PR. CI runs the same checks. - Add a line under
## [Unreleased]inCHANGELOG.mdfor anything a user would notice.
| Branch | Purpose |
|---|---|
alpha |
Active development. All work lands here first, and alpha pre-releases are cut from here. |
main |
Stable releases only. It only changes when a finished alpha cycle is promoted. The README on main is what users see. |
feature/*, fix/*, … |
Your working branches. Create them from alpha and merge them back into alpha. |
Pull requests that target main will be asked to retarget alpha. The only PRs
that go into main are the maintainers' release PRs.
your branch ──PR──▶ alpha ──▶ 1.0.6-alpha.1 ──▶ 1.0.6-alpha.2 ──▶ … (pre-releases)
│
└──PR (alpha → main)──▶ main ──▶ 1.0.6 (stable release)
│
alpha ◀── sync main back ───────────┘
└──▶ 1.0.7-alpha.1 ──▶ … (next cycle starts)
- Development. Contributions are merged into
alpha. - Alpha releases. When enough has landed, a maintainer cuts an alpha pre-release
(
X.Y.Z-alpha.N) fromalpha. Alpha users get it through the in-app updater. - Stable release. When an alpha cycle is ready,
alphais PR'd intomainand merged, and the stableX.Y.Zrelease is cut frommain. - Next cycle.
mainis synced back intoalpha, and the next alpha bumps the patch number:X.Y.(Z+1)-alpha.1.
Why the bump matters: semver ranks
1.0.5above every1.0.5-alpha.N. Publishing1.0.5-alpha.14after the1.0.5stable would look older to the updater and break alpha updates. After a stable release, the next alpha is always1.0.6-alpha.1.
| Tool | Version | Notes |
|---|---|---|
| Node.js | 22.x | Matches the Node runtime bundled with Electron 39 and the version CI uses |
| pnpm | 10.x | Run corepack enable to use the exact version pinned in package.json. Don't use npm or yarn: the lockfile is pnpm-lock.yaml |
| Git | any recent | |
| Go | optional | Only needed for the documentation helper scripts (pnpm docs:check, pnpm docs:combine) |
Every dependency, including the @ghosttypes/ff-api and @parallel-7/slicer-meta
printer libraries, installs from the public npm registry. You don't need a token or an
.npmrc.
git clone https://github.com/Parallel-7/FlashForgeUI-Electron.git
cd FlashForgeUI-Electron
git checkout alpha
git checkout -b feature/my-change
pnpm install # also downloads the bundled go2rtc camera binary
pnpm dev # launches the app with hot reloadIf you fork the repository, keep your fork's alpha up to date with
Parallel-7/FlashForgeUI-Electron:alpha and branch from it.
| Command | What it does |
|---|---|
pnpm dev |
Builds the WebUI and starts the app with hot reload |
pnpm build |
Builds the main process, renderer, and WebUI |
pnpm type-check |
Type-checks the app (src/) and the test tree (tests/) |
pnpm lint / pnpm lint:fix |
Biome lint, or lint plus format with fixes applied |
pnpm test |
Jest unit and integration tests |
pnpm test:e2e |
Browser Playwright tests for the WebUI |
pnpm test:e2e:electron:emulator |
Desktop end-to-end tests against the printer emulator |
pnpm build:win / build:mac / build:linux |
Package installers for a platform |
pnpm docs:check |
Lists source files missing an @fileoverview header (needs Go) |
To run the app without its desktop window (the WebUI server only), see the Headless Mode wiki page.
| Path | Contents |
|---|---|
src/main/ |
Electron main process: printer backends, connection and polling managers, services (camera, Spoolman, notifications, SSH), IPC handlers, windows |
src/main/webui/ |
WebUI: the Express/WebSocket server (server/) and the browser client (static/) |
src/preload/ |
Context bridges between the main process and renderer windows |
src/renderer/ |
Desktop UI: the GridStack dashboard, components, and dialogs |
src/shared/ |
Types and helpers shared across processes |
tests/e2e/ |
Playwright suites (browser WebUI + Electron) |
docs/TESTING.md |
The authoritative guide to every test surface |
ai_docs/ has deeper architecture notes. Start with ARCHITECTURE.md and
MULTI_CONTEXT.md.
- TypeScript everywhere, strict types, no
anywithout a good reason. - Biome handles linting and formatting.
pnpm lintmust pass with no errors. Fix the code rather than suppressing a rule. - File headers: every source file under
src/starts with an@fileoverviewblock. It should say what the file is for, its key exports, and how it relates to nearby modules.pnpm docs:checkfinds files that are missing one. - Never hardcode colors in CSS. Use the theme variables (
--theme-primary,--surface-elevated, …) so light, dark, and custom themes keep working. Seeai_docs/THEME_SYSTEM.md. - One code path for desktop and headless. Both modes share the same connection, polling, and camera stack. Avoid branching on headless mode unless there is no other way.
- WebUI parity. User-facing features should work in the WebUI as well as the desktop app where that makes sense. The WebUI code is shared with the standalone FlashForgeWebUI project, so mention in your PR if a change should be ported there too.
- Gate features on printer capabilities, not on printer names. The printer name is user-editable. Use the backend's feature flags and the model detected at connect time.
- Keep both preloads in sync. If you add IPC channels that pop-out component dialogs need, add them to the component-dialog preload as well as the main preload.
- Printer protocol bugs belong upstream. If the bug is in how the printer API is
spoken, fix it in
ff-5mp-api-ts(@ghosttypes/ff-api), then bump the dependency here. Don't patch around the library.
Read docs/TESTING.md before you write or change a test. It
explains what each suite actually proves and where the known gaps are.
- Unit / integration (
pnpm test): Jest tests live next to the code in__tests__/folders. New logic should come with tests. - Desktop E2E, emulator track (
pnpm test:e2e:electron:emulator): drives the real app against emulated printers (Adventurer 5M Pro, AD5X, Creator 5, Creator 5 Pro) with no hardware needed. It expects a checkout offlashforge-emulator-v2next to this repository. If yours is elsewhere, setFF_EMULATOR_ROOTto its path. CI runs this track on every push and PR. - Desktop E2E, hardware track (
pnpm test:e2e:electron:hardware): the same specs run against real printers on your network. Specs branch on printer capabilities, never on which track is running. Don't fork a spec per track. - Real printers: if your change touches printer communication, please test it on a real printer if you have one, and say which model and firmware you used in the PR.
Commit messages follow Conventional Commits:
feat(webui): add job upload to the browser UI
fix(creator5): show paused state instead of Unknown
chore(deps): bump @ghosttypes/ff-api to 2.0.3
docs: clarify headless setup
Common types: feat, fix, refactor, perf, test, docs, ci, build, chore.
The scope is optional but helpful (webui, spoolman, camera, creator5, …).
Referencing issues: mention the issue number (#123) in the title or body, but
don't use auto-closing keywords like Fixes #123 or Closes #123. Issues
are closed by hand once the fix has been confirmed in a release.
Before opening a PR:
- Branched from, and targeting,
alpha -
pnpm type-check,pnpm lint,pnpm build, andpnpm testall pass - Tests added or updated for the change
-
CHANGELOG.mdupdated under## [Unreleased]for user-visible changes -
@fileoverviewheaders present on new files - Printer model and firmware listed if tested on hardware
- The wiki needs an update for user-facing changes (say so in the PR)
Keep PRs focused: one feature or fix per PR is much easier to review than a bundle.
Releases are built by the Build and Release Electron App workflow
(.github/workflows/release.yml). It runs on workflow_dispatch, builds Windows,
macOS, and Linux, tags v<version>, and publishes a GitHub Release with the updater
manifests. The update channel is derived from the version: X.Y.Z-alpha.N publishes to
the alpha channel, and a bare X.Y.Z publishes to latest.
The release notes are taken from the CHANGELOG.md section whose heading matches the
version exactly: ## [<version>] - <YYYY-MM-DD>. Without that section, the release gets
only a bare link.
- On
alpha, bumpversioninpackage.json(e.g.1.0.6-alpha.1→1.0.6-alpha.2). - Move the
## [Unreleased]notes into a new## [1.0.6-alpha.2] - <date>section, and leave an empty## [Unreleased]above it. - Commit and push to
alpha. - Dispatch the workflow on
alphawithprerelease: true:gh workflow run release.yml --ref alpha -f version=1.0.6-alpha.2 -f prerelease=true
- On
alpha, setversionto the stable number (1.0.6-alpha.N→1.0.6). Add a## [1.0.6] - <date>section that sums up the whole alpha cycle, and make sure the README is current. - Make sure
alphacontains everything onmain(mergemainintoalphaif needed), so the release PR has no conflicts. - Open a PR from
alphaintomain, let CI pass, and merge it. - Live-test the build on real printers.
- Dispatch the workflow on
mainwithprerelease: false:gh workflow run release.yml --ref main -f version=1.0.6 -f prerelease=false - Sync
mainback intoalpha. Then bumpalphato the next patch alpha (1.0.7-alpha.1) with a fresh## [Unreleased]section.
Don't pre-create a draft GitHub Release for the tag. The workflow publishes an existing draft and keeps the draft's body instead of the generated notes.
By contributing, you agree that your contributions will be licensed under the project's MIT License.