flowengine-rails 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. checksums.yaml +7 -0
  2. data/.claude/branch-name.sh +49 -0
  3. data/.claude/commands/create-pr.md +93 -0
  4. data/.claude/commands/stash-unstaged.md +21 -0
  5. data/.claude/commands/unstash-unstaged.md +15 -0
  6. data/.claude/settings.json +72 -0
  7. data/.rubocop_todo.yml +17 -0
  8. data/.ruby-version +1 -0
  9. data/CHANGELOG.md +5 -0
  10. data/CLAUDE.md +153 -0
  11. data/LICENSE.txt +21 -0
  12. data/README.md +294 -0
  13. data/Rakefile +43 -0
  14. data/app/assets/javascripts/flow_engine/embed.js +44 -0
  15. data/app/assets/javascripts/flow_engine/progress_controller.js +17 -0
  16. data/app/assets/javascripts/flow_engine/step_controller.js +22 -0
  17. data/app/assets/stylesheets/flow_engine/application.css +569 -0
  18. data/app/controllers/flow_engine/admin/definitions_controller.rb +95 -0
  19. data/app/controllers/flow_engine/application_controller.rb +41 -0
  20. data/app/controllers/flow_engine/sessions_controller.rb +91 -0
  21. data/app/helpers/flow_engine/sessions_helper.rb +20 -0
  22. data/app/models/flow_engine/application_record.rb +8 -0
  23. data/app/models/flow_engine/flow_definition.rb +75 -0
  24. data/app/models/flow_engine/flow_session.rb +98 -0
  25. data/app/views/flow_engine/admin/definitions/_form.html.erb +27 -0
  26. data/app/views/flow_engine/admin/definitions/edit.html.erb +5 -0
  27. data/app/views/flow_engine/admin/definitions/index.html.erb +41 -0
  28. data/app/views/flow_engine/admin/definitions/mermaid.html.erb +9 -0
  29. data/app/views/flow_engine/admin/definitions/new.html.erb +5 -0
  30. data/app/views/flow_engine/admin/definitions/show.html.erb +25 -0
  31. data/app/views/flow_engine/sessions/completed.html.erb +34 -0
  32. data/app/views/flow_engine/sessions/new.html.erb +17 -0
  33. data/app/views/flow_engine/sessions/show.html.erb +26 -0
  34. data/app/views/flow_engine/sessions/steps/_boolean.html.erb +10 -0
  35. data/app/views/flow_engine/sessions/steps/_display.html.erb +4 -0
  36. data/app/views/flow_engine/sessions/steps/_multi_select.html.erb +8 -0
  37. data/app/views/flow_engine/sessions/steps/_number.html.erb +3 -0
  38. data/app/views/flow_engine/sessions/steps/_number_matrix.html.erb +13 -0
  39. data/app/views/flow_engine/sessions/steps/_single_select.html.erb +8 -0
  40. data/app/views/flow_engine/sessions/steps/_text.html.erb +11 -0
  41. data/app/views/flow_engine/sessions/steps/_unknown.html.erb +4 -0
  42. data/app/views/layouts/flow_engine/application.html.erb +26 -0
  43. data/app/views/layouts/flow_engine/embed.html.erb +30 -0
  44. data/config/routes.rb +22 -0
  45. data/db/migrate/01_create_flow_engine_definitions.rb +18 -0
  46. data/db/migrate/02_create_flow_engine_sessions.rb +18 -0
  47. data/exe/flowengine-rails +4 -0
  48. data/justfile +49 -0
  49. data/lefthook.yml +16 -0
  50. data/lib/flowengine/rails/configuration.rb +23 -0
  51. data/lib/flowengine/rails/dsl_loader.rb +35 -0
  52. data/lib/flowengine/rails/engine.rb +26 -0
  53. data/lib/flowengine/rails/version.rb +7 -0
  54. data/lib/flowengine/rails.rb +27 -0
  55. data/lib/generators/flow_engine/flow/flow_generator.rb +29 -0
  56. data/lib/generators/flow_engine/flow/templates/flow_definition.rb.tt +27 -0
  57. data/lib/generators/flow_engine/flow/templates/seed_task.rake.tt +22 -0
  58. data/lib/generators/flow_engine/install/install_generator.rb +34 -0
  59. data/lib/generators/flow_engine/install/templates/initializer.rb +25 -0
  60. data/log/.gitkeep +0 -0
  61. data/sig/flowengine/rails.rbs +6 -0
  62. metadata +164 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8ff826ab26eb55ed836e66300baf943197a13f17b4d44886ab784a303f919978
4
+ data.tar.gz: dbd3b01d8eff30c0ed0b7b9054ccc5769161afb350f14cdce34bd6b62a6dbf70
5
+ SHA512:
6
+ metadata.gz: 91d37ce06fcaee18b93c05e5a79bafdd891c9906238bc5ad542f1866c3d9abee4263706d70b65c948b76b1fdb999bb74be67a4fdfc5bcdf2071146163960536d
7
+ data.tar.gz: 6b6f9f96accc3f9be1dd01d7b35d8fb6e7a634016d5eb5dccf0639ebc0c2dd39d2136c21dcb719e11433faeae04decedbc0261a9e51dc96b76ed25515b935229
@@ -0,0 +1,49 @@
1
+ #!/bin/bash
2
+
3
+ # ANSI color codes
4
+ BOLD_YELLOW='\033[1;33m'
5
+ BOLD_RED='\033[1;31m'
6
+ RESET='\033[0m'
7
+
8
+ while true; do
9
+ if [[ -z "$*" ]]; then
10
+ # Prompt user (to STDERR)
11
+ echo -n "Please name the branch for your changes: " >&2
12
+
13
+ # Print bold yellow escape sequence
14
+ echo -ne "${BOLD_YELLOW}" >&2
15
+
16
+ # Read user input
17
+ read branch_input
18
+ else
19
+ branch_input="$*"
20
+ fi
21
+
22
+ # Reset color
23
+ echo -ne "${RESET}" >&2
24
+
25
+ # Transform the input:
26
+ # 1. Replace non-alphanumeric (except / and space) with dash
27
+ # 2. Replace sequences of multiple dashes with single dash
28
+ # 3. Convert to lowercase
29
+ branch_name="$(echo "${branch_input}" |
30
+ sed -E 's/[^a-zA-Z0-9/-]/-/g' |
31
+ sed -E 's/-+/-/g; ' |
32
+ tr '[:upper:]' '[:lower:]')"
33
+
34
+ # Prepend $USER/ if not already present
35
+ if [[ ! "$branch_name" =~ ^${USER}/ ]]; then
36
+ branch_name="${USER}/${branch_name}"
37
+ fi
38
+
39
+ # Check length
40
+ if [ ${#branch_name} -gt 50 ]; then
41
+ echo -e "${BOLD_RED}Error: Branch name is too long (${#branch_name} characters, max 50)${RESET}" >&2
42
+ echo >&2
43
+ continue
44
+ fi
45
+
46
+ # Output the branch name to STDOUT
47
+ echo "${branch_name}"
48
+ break
49
+ done
@@ -0,0 +1,93 @@
1
+ ---
2
+
3
+ description: "Create a pull request from current branch or staged changes"
4
+ allowed-tools:
5
+
6
+ - Bash(./.claude/branch-name.sh)
7
+ - Bash(just check-all:\*)
8
+ - Bash(git \*)
9
+ - Bash(gh \*)
10
+
11
+ ---
12
+
13
+ # Create One or More Pull Request from the Currently Modified or Staged Files
14
+
15
+ IMPORTANT: NON-INTERACTIVITY. This command should complete nearly always without human interaction. Only if you are really confused or lacking a major permission should you pause and ask question. Assume that the human is away from the keyboard while you are submitting their changes as PRs.
16
+
17
+ ## Command's Purpose
18
+
19
+ The purpose of this command is to take all locally modified files (staged or not), split them into unrelated, or related but stackable commits and PRs, and do it non-interactively, and do it in such a away that both locally and on Github CI (if there is one) each PR passes, does not have merge conflicts, and should be ready to be reviewed and merged.
20
+
21
+ ## Steps to Accomplish
22
+
23
+ 1. First, feel free to `git add . ` so that all the modified files are staged. This command is deliberately mean to commit ALL modified files for simplicity, otherwise it's difficult to identify what should or should not be committed.
24
+ 2. Second, if the repo supports command `just format` or `make format` or `rubocop -a` (or other safe linter fix commandss) — go ahead and run them now. If there are locally modified files, `git add` them.
25
+ 3. If we are NOT on the main branch, then do a diff between the current branch and the main (excluding the staged files) to understand what the context of the current branch is.
26
+ 4. Next you are going to review all the staged files and break them up by "context": i.e. that means they need to be committed and pushed to a PR together. This will also help you identify which files should be committed into the current branch (i.e. they all relate to the changes on this branch, or the branch name). If there are several unrelated changes in this set of staged files, OR multiple related changes, but there are a lot of changes (the diff is > 500 lines) you will need to break up this PR into multiple PRs.
27
+
28
+ Multiple PRs can either be on branches that are created off main (those changes should have nothing to do with the current branch), OR they can be committed and pushed as multiple PRs that are stacked on each other.
29
+
30
+ For every commit and branch that you push you must ensure that local tests are passing and if not either fix them (if that's easy) or refuse to push a PR until you pairing with the developer resolve all test issues.
31
+
32
+ Tests are typically invoked as:
33
+
34
+ ```
35
+ just test # if there is a justfile at the root
36
+ make test # if there is a Makefile at the root
37
+ bundle exec rspec --format documentation # if this is a ruby repo, meaning there is a Gemfile and Gemfile.lock at the root level and spec folder
38
+ npm run test # if there is package.json and a test command
39
+ # for other types of repos identify the most common ways to run tests, and do run them before committing any code.
40
+ ```
41
+
42
+ ## Branch Names
43
+
44
+ We provided a convenient shell script in the .claude directory, called `./.claude/branch-name.sh`
45
+
46
+ To generate a new branch name, identify from three to six key words describing this change and then run from the project root:
47
+
48
+ ```bash
49
+ $ .claude/branch-name.sh add copilot LLM bubble for admins # this script will output the following
50
+ kig/add-copilot-llm-bubble-for-admins
51
+ ```
52
+
53
+ If you run this script, capture it's STDOUT and you'll get the branch name.
54
+
55
+ It prefixes the current user `$USER` and a slash, and then dash-joins all the words you specified.
56
+
57
+ If you need information on how to split one large PR into smaller ones, the following URLs might be helpful:
58
+
59
+ - https://www.davepacheco.net/blog/2025/stacked-prs-on-github/
60
+ - https://itsnotbugitsfeature.com/2019/10/22/splitting-a-big-pull-request-into-smaller-review-able-ones
61
+ - https://newsletter.pragmaticengineer.com/p/stacked-diffs
62
+
63
+ ## Committing Changes and Creating the PR
64
+
65
+ Once we are on the appropriate branch, and either there are only changes staged for commit or NO changes at all (so we'll be creating the PR from the branch).
66
+
67
+ ## Rebase from main?
68
+
69
+ Compare the local main with remote main, and if they diverged, you'll want to stash all the changes, switch to main, do git pull, then restore the branch, and before you pop the stash, run `git rebase main`. Resolve any conflicts that you are able to do on your own, otherwise this is one case where you can engage interactively and ask the developer for help. Once the current branch is rebased, you `git stash pop`. This may also result in conflicts. The same thing: attempt to resolve them and commit the rebase, or engage with the developer.
70
+
71
+ ### Committing Currently Staged Changes
72
+
73
+ At this point, you should either a clean branch with all changes committed, or some changes staged for commit.
74
+
75
+ If there are changes staged for commit, you must perform `git diff --cached`, and summarize those changes in the markdown format in a temporary file we'll call <tempfile> (preferably under /tmp folder), and create a short title for the commit.
76
+
77
+ Then we commit with `git commit -m "<title>" -F "<tempfile>"`, and push the changes `git push -u origin`.
78
+
79
+ ### Creating the PR
80
+
81
+ This is the final step. You are to use command `gh` (which stands for `github`).
82
+
83
+ In order to create a PR we must NOT be on the `main` branch, we must have all changes pushed to the origin and we must have no locally untracked or modified files.
84
+
85
+ The next step is describing the PR.
86
+
87
+ You are going to perform the diff of the current branch with the remote `main` branch using `git diff origin/main...HEAD`, and analyze it. You will create a markdown file that we'll refer to <pr-description> preferably in the `/tmp` folder, that describes the changes of this PR precisely, professionally, without any emojis. It should have the top header title that we'll refer to <pr-title>, (with a single '#' in markdown) that will also be the name of the PR. It should have the sections such as Summary (a short abstract-type description, no more than a few paragraphs) Description (if necessary, a more detailed description), Motivation, Testing, Backwards Compatibility, Scalability & Performance Impact, and Code Quality Analysis.
88
+
89
+ Once completed analysis and summarizing of this change, you will invoke the command `gh pr create -a @me -B main -F "<pr-description>" -t "<pr-title>"`.
90
+
91
+ If the command responds with an error such as Github Token authorization is insufficient, you are to save the PR description in the file at the root of the project called `PR.md` and report this to the user. Also print the URL required to create the PR on the web by clicking on the URL.
92
+
93
+ If you were able to create the PR with the `gh pr create` command, then open the web page to it anyway. Get the PR ID with `gh pr list | grep "<pr-title>" | awk '{print $1}'`
@@ -0,0 +1,21 @@
1
+ ---
2
+
3
+ description: "Stash unstaged/untracked files while preserving staged changes"
4
+ allowed-tools:
5
+
6
+ - Bash(git \*)
7
+
8
+ ---
9
+
10
+ # Stash Unstaged Changes
11
+
12
+ This command stashes unstaged and untracked files while preserving any files already staged for commit.
13
+
14
+ Check if there are any files staged for commit (i.e. git added) with `git diff --cached`
15
+
16
+ - If there are none, simply run `git stash push --include-untracked`
17
+ - If there are unstaged changes in the workspace in addition to staged, you are to perform the following series of commands:
18
+ 1. `git commit -m 'WIP: staged changes' --no-verify` — commit the staged changes temporarily
19
+ 1. `git stash push --include-untracked` — to stash the remaining unstaged changes
20
+ 1. `git reset --soft HEAD^` — to undo the temporary commit
21
+ 1. `git add .` — to stage the previously staged for commit files
@@ -0,0 +1,15 @@
1
+ ---
2
+
3
+ description: "Restore previously stashed unstaged changes"
4
+ allowed-tools:
5
+
6
+ - Bash(git \*)
7
+
8
+ ---
9
+
10
+ # Unstash Unstaged Changes
11
+
12
+ This command brings back the changes made by the "stash-unstaged" command.
13
+
14
+ Ensure that the current workspace is clean (no staged or unstaged files) and if there are any — ask if the user still wants to proceed.
15
+ If the user confirms, run `git stash apply` command to bring back previously stashed changes.
@@ -0,0 +1,72 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Edit",
5
+ "Bash(*)",
6
+ "Bash(chmod:*)",
7
+ "Skill(create-pr)",
8
+ "WebFetch(domain:docs.tabler.io)",
9
+ "WebFetch(domain:smartdatalake.ch)",
10
+ "WebSearch",
11
+ "WebFetch(*)",
12
+ "WebFetch(domain:github.com)",
13
+ "WebFetch(domain:raw.githubusercontent.com)",
14
+ "WebFetch(domain:www.vcpr.cz)",
15
+ "WebFetch(domain:renju.net)",
16
+ "WebFetch(domain:en.wikipedia.org)"
17
+ ]
18
+ },
19
+ "enabledPlugins": {
20
+ "activerecord@claude-ruby-marketplace": true,
21
+ "build@motlin-claude-code-plugins": true,
22
+ "chrome-devtools-mcp@chrome-devtools-plugins": true,
23
+ "ci-cd@devops-skills": true,
24
+ "example-skills@anthropic-agent-skills": false,
25
+ "feature-dev@claude-code-plugins": true,
26
+ "frontend-design@claude-code-plugins": true,
27
+ "git@motlin-claude-code-plugins": true,
28
+ "iac-terraform@devops-skills": true,
29
+ "justfile@motlin-claude-code-plugins": true,
30
+ "pr-review-toolkit@claude-code-plugins": true,
31
+ "rspec@claude-ruby-marketplace": true,
32
+ "ruby-lsp@ruby-skills": true,
33
+ "ruby-skills@ruby-skills": true,
34
+ "security-guidance@claude-code-plugins": true,
35
+ "superpowers-developing-for-claude-code@superpowers-marketplace": true,
36
+ "superpowers@superpowers-marketplace": false
37
+ },
38
+ "extraKnownMarketplaces": {
39
+ "ruby-skills": {
40
+ "source": {
41
+ "source": "github",
42
+ "repo": "st0012/ruby-skills"
43
+ }
44
+ },
45
+ "claude-ruby-marketplace": {
46
+ "source": {
47
+ "source": "github",
48
+ "repo": "hoblin/claude-ruby-marketplace"
49
+ }
50
+ },
51
+ "devops-skills": {
52
+ "source": {
53
+ "source": "github",
54
+ "repo": "ahmedasmar/devops-claude-skills"
55
+ }
56
+ },
57
+ "motlin-claude-code-plugins": {
58
+ "source": {
59
+ "source": "github",
60
+ "repo": "motlin/claude-code-plugins"
61
+ }
62
+ },
63
+ "chrome-devtools-plugins": {
64
+ "source": {
65
+ "source": "github",
66
+ "repo": "ChromeDevTools/chrome-devtools-mcp"
67
+ }
68
+ }
69
+ },
70
+ "effortLevel": "high",
71
+ "skipDangerousModePermissionPrompt": true
72
+ }
data/.rubocop_todo.yml ADDED
@@ -0,0 +1,17 @@
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config`
3
+ # on 2026-03-17 03:19:43 UTC using RuboCop version 1.85.1.
4
+ # The point is for the user to remove these configuration records
5
+ # one by one as the offenses are removed from the code base.
6
+ # Note that changes in the inspected code, or installation of new
7
+ # versions of RuboCop, may require this file to be generated again.
8
+
9
+ # Offense count: 2
10
+ # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes.
11
+ Metrics/AbcSize:
12
+ Max: 20
13
+
14
+ # Offense count: 1
15
+ # Configuration parameters: AllowedMethods, AllowedPatterns.
16
+ Metrics/CyclomaticComplexity:
17
+ Max: 9
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 4.0.1
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2026-02-26
4
+
5
+ - Initial release
data/CLAUDE.md ADDED
@@ -0,0 +1,153 @@
1
+ # FlowEngine Rails
2
+
3
+ ## What This Gem Does
4
+
5
+ `flowengine-rails` is a Rails Engine (v0.1.0) that wraps the `flowengine` core gem with ActiveRecord persistence, a Hotwire-based web wizard UI, an admin CRUD interface, and an iframe-embeddable widget. It lets non-technical users define multi-step form flows via a Ruby DSL, store them in the database, and serve them to end users as interactive step-by-step wizards.
6
+
7
+ The target application is **Qualified.at** -- a lead qualification service where flows are embedded in external client sites via iframes.
8
+
9
+ ## Dependency on `flowengine` Core Gem
10
+
11
+ The core gem (`flowengine`, published on RubyGems as v0.4.0, source at `github.com/kigster/flowengine`) provides:
12
+
13
+ - **DSL** for defining flows: `FlowEngine.define { ... }` with steps, transitions, rules
14
+ - **Engine** (state machine): `FlowEngine::Engine` drives step traversal, answers, history
15
+ - **Node** objects representing individual steps with types (`:text`, `:boolean`, `:number`, `:single_select`, `:multi_select`, `:number_matrix`, `:display`)
16
+ - **Graph/MermaidExporter** for diagram visualization
17
+ - **Rules/Evaluator** for conditional transitions (`if_rule:`, `contains()`, etc.)
18
+ - **Validation** for DSL correctness
19
+
20
+ This gem calls into `flowengine` via:
21
+ - `FlowEngine.load_dsl(dsl_text)` -- parses DSL string into a Definition
22
+ - `FlowEngine::Engine.new(definition)` / `FlowEngine::Engine.from_state(definition, state)` -- creates/restores engine
23
+ - `engine.answer(value)`, `engine.to_state`, `engine.finished?` -- drives flow
24
+ - `definition.step_ids`, `definition.step(id)` -- introspects steps
25
+ - `FlowEngine::Graph::MermaidExporter` -- generates Mermaid diagrams
26
+
27
+ **Local development**: `Gemfile` uses `path: "../flowengine"` (sibling directory). **CI/branches**: changed to `github: "kigster/flowengine"`. The gemspec declares `spec.add_dependency "flowengine", "~> 0.1"`.
28
+
29
+ ## Repository Layout
30
+
31
+ ```
32
+ flowengine-rails.gemspec # Gem metadata, deps: flowengine ~> 0.1, rails >= 8.0.1, stimulus-rails, turbo-rails
33
+ Gemfile # Dev deps: rspec-rails, rspec-its, rubocop, capybara, simplecov, sqlite3
34
+ Rakefile # Default task: spec + rubocop
35
+
36
+ lib/
37
+ flowengine/rails.rb # Entry point, Configuration singleton, Error class
38
+ flowengine/rails/version.rb # VERSION = "0.1.0"
39
+ flowengine/rails/configuration.rb # embed_allowed_origins, layouts, cache, callbacks, admin auth
40
+ flowengine/rails/dsl_loader.rb # Thread-safe in-memory cache around FlowEngine.load_dsl
41
+ flowengine/rails/engine.rb # Rails::Engine with asset paths, importmap, generator config
42
+ generators/
43
+ flow_engine/install/ # rails g flow_engine:install -- migrations, initializer, route mount
44
+ flow_engine/flow/ # rails g flow_engine:flow NAME -- definition file + seed rake task
45
+
46
+ app/
47
+ models/flow_engine/
48
+ application_record.rb # Base AR class
49
+ flow_definition.rb # name, version, dsl (text), active (boolean). Validates DSL parses.
50
+ # Auto-increments version on create. activate!/deactivate! toggles.
51
+ # readonly? when sessions exist. Generates mermaid diagrams.
52
+ flow_session.rb # belongs_to definition. Tracks current_step_id, answers (JSON),
53
+ # history (JSON), status (in_progress/completed/abandoned).
54
+ # advance!(answer) drives the engine. fire_completion_callback on complete.
55
+ controllers/flow_engine/
56
+ application_controller.rb # Embed mode detection (?embed=true), CORS headers, layout switching
57
+ sessions_controller.rb # new, create, show, update, completed, abandon
58
+ # Parses answer by step type (multi_select->array, number->int, etc.)
59
+ admin/definitions_controller.rb # Full CRUD + activate/deactivate/mermaid. Admin auth via config.
60
+ helpers/flow_engine/
61
+ sessions_helper.rb # render_step(node, form) dispatches to type-specific partials
62
+ views/
63
+ flow_engine/sessions/ # new, show, completed templates
64
+ flow_engine/sessions/steps/ # Partials: _boolean, _text, _number, _single_select,
65
+ # _multi_select, _number_matrix, _display, _unknown
66
+ flow_engine/admin/definitions/ # index, show, new, edit, _form, mermaid
67
+ layouts/flow_engine/ # application.html.erb (standalone), embed.html.erb (iframe)
68
+ assets/
69
+ javascripts/flow_engine/ # embed.js (iframe resizer), progress_controller.js, step_controller.js
70
+ stylesheets/flow_engine/ # application.css (full standalone stylesheet)
71
+
72
+ config/routes.rb # sessions (new/create/show/update + completed/abandon)
73
+ # admin/definitions (CRUD + activate/deactivate/mermaid)
74
+ # root -> sessions#new
75
+
76
+ db/migrate/
77
+ 01_create_flow_engine_definitions.rb # name, version, dsl, active. Unique index on [name, version].
78
+ 02_create_flow_engine_sessions.rb # definition_id (FK), current_step_id, answers/history/metadata (JSON), status
79
+
80
+ spec/
81
+ spec_helper.rb # SQLite in-memory, schema created inline, SIMPLE_DSL fixture
82
+ dummy/ # Minimal Rails 8 app (SQLite :memory:, engine mounted at /flow_engine)
83
+ controllers/ # Request specs for sessions + admin/definitions
84
+ models/ # Unit specs for FlowDefinition, FlowSession
85
+ lib/ # Specs for Configuration, DslLoader
86
+ routing/ # Route specs
87
+
88
+ .github/workflows/
89
+ main.yml # Ruby 3.4.4, bundle exec rake (spec + rubocop)
90
+ rspec.yml # Ruby 4.0, checks out kigster/flowengine alongside, bundle exec rspec
91
+ rubocop.yml # Ruby 4.0, bundle exec rubocop
92
+ ```
93
+
94
+ ## Database Schema
95
+
96
+ **flow_engine_definitions**: `name` (string), `version` (integer, auto-incremented per name), `dsl` (text, Ruby DSL source), `active` (boolean). Unique index on `[name, version]`.
97
+
98
+ **flow_engine_sessions**: `definition_id` (FK), `current_step_id` (string), `answers` (JSON), `history` (JSON, array of visited step IDs), `status` (string: in_progress/completed/abandoned), `metadata` (JSON).
99
+
100
+ ## Configuration
101
+
102
+ ```ruby
103
+ FlowEngine::Rails.configure do |config|
104
+ config.embed_allowed_origins = ["https://example.com"] # CORS for iframe embed
105
+ config.default_layout = "flow_engine/application" # Standalone layout
106
+ config.embed_layout = "flow_engine/embed" # Iframe layout
107
+ config.cache_definitions = true # In-memory DSL parse cache
108
+ config.on_session_complete = ->(session) { ... } # Completion callback
109
+ config.admin_authentication_method = :authenticate_admin! # Admin before_action
110
+ end
111
+ ```
112
+
113
+ ## Step Types and Answer Parsing
114
+
115
+ | Step type | View partial | Answer param | Parsed as |
116
+ |-----------------|--------------------|-----------------------|----------------------|
117
+ | `:text` | `_text` | `params[:answer]` | String |
118
+ | `:number` | `_number` | `params[:answer]` | Integer (`.to_i`) |
119
+ | `:boolean` | `_boolean` | `params[:answer]` | Boolean (`== "true"`)|
120
+ | `:single_select`| `_single_select` | `params[:answer]` | String |
121
+ | `:multi_select` | `_multi_select` | `params[:answer_values]` | Array |
122
+ | `:number_matrix`| `_number_matrix` | `params[:answer_fields]` | Hash (string->int)|
123
+ | `:display` | `_display` | (none, info-only) | N/A |
124
+
125
+ ## Running Tests and Linting
126
+
127
+ ```bash
128
+ bundle exec rspec # Tests (SQLite in-memory, no external DB needed)
129
+ bundle exec rubocop # Lint
130
+ bundle exec rake # Both (default task)
131
+ ```
132
+
133
+ ## CI Notes
134
+
135
+ - **Gemfile.lock must include `x86_64-linux` platform** (`bundle lock --add-platform x86_64-linux`)
136
+ - The `rspec.yml` workflow checks out `kigster/flowengine` as a sibling directory for the path dependency
137
+ - The `main.yml` workflow runs on Ruby 3.4.4; `rspec.yml` and `rubocop.yml` run on Ruby 4.0
138
+ - Required Ruby version: `>= 4.0.1` (gemspec)
139
+
140
+ ## RuboCop Configuration
141
+
142
+ - Target Ruby 4.0, double quotes enforced, frozen string literal required
143
+ - Line length max 120, method length max 20
144
+ - `spec/dummy/` excluded from cops
145
+ - `inherit_from: .rubocop_todo.yml`
146
+
147
+ ## Key Architectural Patterns
148
+
149
+ - **Isolated namespace**: all code under `FlowEngine` module, engine uses `isolate_namespace FlowEngine`
150
+ - **Immutable definitions**: once a `FlowDefinition` has sessions, it becomes `readonly?`; users must create a new version
151
+ - **State reconstruction**: `FlowSession#engine` rebuilds `FlowEngine::Engine` from persisted state on every request (stateless controller pattern)
152
+ - **Embed mode**: `?embed=true` query param switches layout and enables CORS headers
153
+ - **Thread-safe caching**: `DslLoader` uses `Mutex` for DSL parse cache
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Konstantin Gredeskoul
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.