Skip to content

feat(recipes): add recipe management with download/upload of tag values - #2492

Open
JordiB-Inserma wants to merge 40 commits into
frangoteam:masterfrom
JordiB-Inserma:recipes
Open

feat(recipes): add recipe management with download/upload of tag values#2492
JordiB-Inserma wants to merge 40 commits into
frangoteam:masterfrom
JordiB-Inserma:recipes

Conversation

@JordiB-Inserma

Copy link
Copy Markdown
Contributor

Description

Adds a Recipes feature to FUXA: manage recipe types vs instances and push/pull tag values to/from devices.

  • Recipe management: type templates define default tag values; instances store actual values per recipe.
  • Download / Upload: push current instance values to the PLC or pull live PLC values into the instance, with Socket.IO progress and cancel support.
  • Editor widget: new HtmlRecipeView gauge control with per-user permissions and configurable visible actions (New, Delete, Save, Download, Upload).
  • Import / Export: CSV/JSON round-trip (with formula-injection guard) for types and instances.
  • Docs added in docs/HowTo-Recipes.md.

The implementation went through two adversarial fresh-context reviews; all findings (1 CRITICAL + 7 WARNINGs + 5 SUGGESTIONs across both rounds) were fixed and verified:

  • Editable default values in the recipe editor; empty values never written as '' to numeric/bool tags (server-side coercion boundary).
  • Cancel/re-run race: generation counter suppresses stale progress/complete/canceled events between superseded runs, including cancel landing during the final device op; upload does not persist partial data on cancel.
  • Concurrent start TOCTOU: losing caller receives a definitive 409 instead of a hung dialog.
  • Widget handlers filter events by captured run instance, so unrelated recipes / navigation cannot misfire or leave the widget stuck.
  • UTF-8 BOM handled on import; widget strings localized (en/es); debug logs removed.

Type of Change

  • Bug fix
  • New feature
  • Refactoring
  • Documentation
  • Other

Build Artifacts Check

  • I did NOT commit /client/dist
  • I did NOT commit generated Angular build output
  • Only source files are included

Checklist

  • Code follows project coding standards
  • I tested my changes locally (server: 195 tests passing; client: ng build AOT clean)
  • Documentation updated if required (docs/HowTo-Recipes.md)
  • Issue opened (for major changes)

Documentation Checklist (if applicable)

  • The documentation builds locally with mkdocs serve
  • All links were tested
  • Images use relative paths (e.g. images/example.png)
  • No references to the old GitHub Wiki
  • Navigation updated in mkdocs.yml (if required)

Additional Notes

Main files:

File Change
server/runtime/recipes/recipe-service.js Download/upload execution, generation counter, cancel handling
server/runtime/recipes/recipe-storage.js Recipe/type persistence
server/api/recipes/index.js REST API + import/export (CSV/JSON)
client/src/app/recipes/* Management pages, editor, progress dialog
client/src/app/gauges/controls/html-recipe/* Editor widget (permissions, visibility)
docs/HowTo-Recipes.md Usage documentation

The generation re-check ran only at the top of the download/upload loop.
If a cancel landed while the final entry's setTagValue/getTagValue was in
flight, the loop exited naturally with canceled still false, so upload
persisted a half-read recipe (the behavior 900dea20 claimed to prevent)
and both directions emitted *_COMPLETE on a cancelled run. For a 1-entry
recipe this was the normal cancel path.

Re-check the running generation after every entry's device op and treat a
mismatch as cancel/supersede: emit RECIPE_CANCELED only when no successor
owns the slot, skip persistence and skip the COMPLETE emit.
…undary

Only the two client save paths sanitized ''; the server still accepted and
stored a literal '' from import (CSV/JSON round-trip, hand-made CSV with an
empty cell), direct API POST, or any recipe saved before the earlier fix.
Those '' values were written verbatim via setTagValue, violating the
invariant that a download never writes '' to a numeric/bool tag.

Map '' to the type's neutral default in coerceValue (0 for numeric families,
false for bool) so the download boundary is safe regardless of what was
ever stored, and coerce '' in place during recipe validation so new saves
(import or POST) store 0/false instead of ''. String tags still legitimately
keep ''.
@unocelli

unocelli commented Aug 6, 2026

Copy link
Copy Markdown
Member

Hi @JordiB-Inserma Great job on this feature, thanks for putting it together. The recipe type + recipe instances model looks like a really useful addition for FUXA.

I’d like to help refine this PR before merge. I found a couple of functional edge cases, and I also think the UX could be aligned more closely with the existing FUXA layout.

Since maintainers are allowed to edit this PR, I’d prepare a few focused commits on top of your branch, keeping functional fixes, wording, busy states, and layout changes separated so we can discuss them individually.

Would that work for you?

@JordiB-Inserma

Copy link
Copy Markdown
Contributor Author

Hi @unocelli,

Thank you for the review and for the positive feedback, I really appreciate it.

Your proposal sounds great to me. Feel free to prepare the commits on top of my branch and keep the different types of changes separated as you suggested. I think that will make the review process much easier.

I'm happy to help with anything related to this feature, whether it's reviewing the proposed changes, providing context on the implementation, testing, or discussing alternative approaches. The same applies to any of the maintainers involved in the review.

Thanks again for taking the time to improve the PR. I look forward to collaborating on it.

@unocelli

unocelli commented Aug 7, 2026

Copy link
Copy Markdown
Member

Hi @JordiB-Inserma I think we should clarify the recipe data model before going too far with the UX.

Currently both recipe types and recipe instances are stored in recipes.db, and each instance stores a full copy of the entries. This means that if a recipe type/template is changed later, for example by adding or removing tags, existing instances are not updated and the widget keeps showing the entries stored in the instance.

This also raises an ownership question. Recipe types reference project tags, so they look closer to project configuration, similarly to alarm definitions in FUXA. Alarms keep their definitions in the project, while runtime state/history is stored separately in the DB.

Maybe recipes should follow a similar split:

  • recipe types/templates live in the project and define the tag structure
  • recipe instances live in recipes.db and store only the values for each template tag

That would make project export/import, backup, tag consistency, and template updates easier to reason about.

In a second step, recipes.db could also be a good place for operational history, such as download/upload executions, user, timestamp, result, and per-tag errors. But I think that can come later, after the core data model is clear.

What do you think if I work on this change as part of the PR?

…ecipes.db.

Split the API into explicit template and instance endpoints, and apply
admin-only protection to template changes in secure mode.
@JordiB-Inserma

Copy link
Copy Markdown
Contributor Author

Hi @unocelli, your proposal looks great to me.

I had planned to tackle this topic in later iterations, but I get that your goal is to ship a much more mature feature from the start. The idea of splitting templates and instances makes total sense, and it gives us a cleaner and more scalable foundation for the data model.

Let me share a few thoughts I had in mind for future improvements, which might be worth considering now that you’re opening the door to a deeper redesign:

One thing I’ve been thinking about is enforcing a stricter data structure for recipe templates. That would allow us to define min/max values, defaults, and proper validation rules for each field. With that in place, instances would behave more predictably and we’d avoid runtime inconsistencies.

I also had the idea of adding template versioning. This would let us track which template version was used to create each instance, ensuring that template updates don’t silently affect existing instances. On the opposite end, we could enable a stricter mode where instances must be validated against the current template, and if the template has changed, the instance would need to be updated as well.

Overall, I think all these points align nicely with the direction you’re proposing and could help reinforce the model even further.

I’m fully open to collaborating on this evolution of the system and happy to work together on the PR or any adjustments needed.

@unocelli

Copy link
Copy Markdown
Member

Hi @JordiB-Inserma, thanks for the feedback.

I agree with your points, especially around stricter template validation and versioning. They fit well with the direction of the redesign.

I’ve pushed a first WIP iteration to your branch focused on the foundation: splitting templates and instances, aligning storage/API/security with that model, and keeping the scope manageable for this step.

Please don’t hesitate to review it critically or push back on anything you’d like to discuss. We can iterate together from there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants