Zhupi222
HomeAboutProjectsTechnical writingContact information

Why We Moved Deployment Differences into a Recipe System

2026-07-21T00:00:00.000Z约8分钟
Starting with real differences on a photo confirmation page, this article explains why the desktop application adopted Recipes and how remote config affected caching and asset timing.
Electron前端架构配置系统项目复盘

The desktop photo application runs at several venues. Capture, confirmation, generation, and printing follow the same main flow, but each venue has its own page requirements.

On the photo confirmation page, one venue may allow a retake while another hides the retake button. Backgrounds and instructions also change. Some differences affect navigation: a venue may skip confirmation or show an extra message before printing. When there were only one or two deployments, the quickest solution was to check the current venue inside the component.

As more venues were added, those checks became expensive to maintain. A normal button change required verifying every deployment condition. Replacing one background still meant changing code, rebuilding the application, and updating devices. Images, copy, and switches were data, but they continued to travel with application releases.

That was the point at which Recipes entered the project.

Why splitting components was not enough

Smaller components could shorten individual files, but deployment differences would remain. Separate venue components would still require frontend changes for every new deployment, and a fix to the shared flow would have to be carried across several versions.

The project already had one common flow. It made more sense to keep that flow and store the changing parts separately. At startup the application identifies the current project and loads its configuration. The same page components then receive different backgrounds, copy, positions, and flow switches.

We called that configuration a Recipe: the instructions for assembling this application for one deployment.

What a Recipe contains

The final model has four parts. tokens hold component styles and asset names. cssVars hold global theme values. slots select which component occupies a position. behavior contains switches that change the flow.

A shortened example makes the roles clearer:

const recipe = { tokens: { photoConfirm: { backgroundImage: 'confirm-background.png', confirmButtonText: 'Use this photo', }, }, slots: { photoConfirmFooter: 'ConfirmActions', }, behavior: { allowRetake: false, skipPhotoConfirm: false, }, };

The confirmation page no longer asks which venue it belongs to. It reads the photoConfirm configuration. Flow code does not need venue names either; it checks allowRetake or skipPhotoConfirm.

Adding a venue now mostly means preparing another Recipe. The components remain shared and do not refer to one another's deployment names.

How a Recipe reaches the device

During startup, the device requests the Recipe for its project ID. A successful response is cached under Electron's user-data directory. The cache writer creates a temporary file and renames it after the write completes, avoiding a partial JSON file after an interrupted process.

If the next startup has no network, the application reads that cache. A new device with neither network data nor cache uses the default Recipe bundled with the installer.

server Recipe request fails → previously cached Recipe no cache → bundled default Recipe

Bundled defaults therefore have to stay complete. Adding a field to server JSON also requires a usable value in the default Recipe.

Why flow switches do not mix with local defaults

Presentation and flow switches use different override rules.

If a remote Recipe omits a style field, the page can continue with a default background or class name. A missing flow switch is riskier. If allowRetake is absent remotely while the bundled default is true, the device may expose an action that the venue did not intend to allow.

For that reason, once the server provides behavior, the application uses the complete remote behavior object. It does not fill missing fields from local behavior. The remote document requires more care, but one object explains the running flow.

const remoteBehavior = recipeData?.behavior; if (remoteBehavior) return remoteBehavior; return resolveUiRecipe(appConfig).behavior;

Configuration and assets were not ready at the same time

Recipe JSON is small and usually arrives quickly. Background images and videos are downloaded separately and take longer.

The first implementation converted remote asset names into local static URLs as soon as the Recipe arrived. The ZIP could still be downloading while components requested files that did not exist, leaving occasional empty images.

Remote tokens were later delayed until asset initialization finished. During download, the provider continues to use bundled pages. It switches to the remote Recipe only after initDone becomes true.

if (!recipeData?.tokens || !assetResolver || !initDone) { return resolvedLocalTokens; } const remoteTokens = resolveAssets(recipeData.tokens, assetResolver); return { ...resolvedLocalTokens, ...remoteTokens };

This removed missing images, but the default theme could still flash before the venue theme appeared. The loading overlay was then extended to cover both the Recipe request and asset synchronization. By the time the page becomes visible, its configuration and files are ready together.

Storybook handles editing and review

As Recipes grew, editing raw JSON became error-prone and gave little visual feedback. Storybook gained tools for switching Recipes, adjusting fields, inspecting pages, and exporting JSON.

The first panels lived entirely in the application repository and carried substantial state and export logic. Shared authoring code was later extracted. One migration removed roughly 550 lines of project glue, and later work removed the older behavior and slot panels. The application now keeps only its field types and defaults.

Storybook is used for editing and preview. Exported Recipes still go to the backend and are delivered during device startup.

The result

The deployments now share the same pages and photo flow. Their differences live in individual Recipes, so changing images, copy, or an existing switch no longer adds another venue condition to a shared component.

Devices can use cached configuration when the remote request fails and bundled defaults when no cache exists. Remote presentation waits for its files before becoming visible. Problems that used to be scattered across components, config files, and asset paths now have defined owners.

There are still gaps. Remote behavior expects a complete object and needs stronger runtime validation. Recipe versions and asset versions are also tracked separately, so troubleshooting checks both.

Recipes fit this project because several venues share one long-lived flow while maintaining their differences independently. If every deployment had a completely different workflow, or a page was deployed only once, using the same Recipe model would add complexity without the same benefit.

PreviousAsset Sync on Desktop Devices: First Full Download, Incremental Threshold, and Local Fallback

Related posts

Asset Sync on Desktop Devices: First Full Download, Incremental Threshold, and Local Fallback
2026-07-18T00:00:00.000Z约9分钟
Electron资源同步离线运行