Skip to content

Next-gen: javaabu/workflows v1.0 #17

Open

Description

@n4ff4h

Dynamic workflow builder: workflows defined fluently and seeded, or built visually by drag and drop, with developer-defined actions, conditionals, form-builder data, a workflow tester and an optional AI assistant.

Package: javaabu/workflows · Target: v1.0 · Wave: 5
Depends on: javaabu/form-builder, javaabu/permissions, javaabu/manifest
Optional: javaabu/translatable, laravel/ai

Built to the next-gen general rules: CI with test and coverage badges, full docs for people and agents, Laravel Boost guideline and skill, Javaabu codestyle, PHP 8.4 / Laravel 12+ (Laravel packages) or TypeScript (JS packages), Dhivehi and RTL, Blade and Inertia React for views, overridable controllers and policies with javaabu/permissions.

Checklist

Developed by an agent orchestrated from the Javaabu next-gen plan. Cross-package requests go in the dependency's repo with the cross-package label.

Activity

  1. added
    next-genNext-gen Javaabu packages
    epicTracks a whole package
    on Nov 4, 2026
  2. n4ff4h commented on Nov 5, 2026

    @n4ff4h
    CollaboratorAuthor

    Implementation plan

    Laravel package javaabu/workflows, namespace Javaabu\Workflows\, structured like javaabu/form-builder (its closest sibling): PHP + TypeScript in resources/js shipped through javaabu/manifest, with a committed dist/ for the Blade entries.

    Architecture

    • Definitions and versions (Workflow engine #2, Fluent definitions and seeding #3). workflows (key, name, description, subject type, status, published_version_id, locked, settings, soft deletes, translatable columns) and workflow_versions (number, definition JSON, published_at, author morph). A workflow has at most one draft, which the builder edits; published versions are immutable, and instances keep the version they started on. The definition is one validated JSON document per version:
      {"workflows": 1, "steps": [{"key": "review", "type": "task", "name": "Manager review", "assignees": [{"type": "role", "value": "manager"}], "form": "leave-review", "actions": [], "position": {"x": 320, "y": 80}}],
       "transitions": [{"key": "approve", "from": "review", "to": "approved", "label": "Approve", "color": "success", "conditions": {"match": "all", "rules": []}, "comment": "optional", "actions": []}]}
    • Step types. start (where an instance begins; optional start form filled by whoever starts it), task (waits for a person: assignees, optional form, the person picks an outgoing transition), decision (automatic: follows the first outgoing transition whose conditions hold, else the one without conditions), action (automatic: runs its actions, then moves on like a decision), end (completes the instance with an outcome).
    • Instances (Workflow engine #2). workflow_instances (version, subject morph, starter morph, current step, status active/completed/cancelled, outcome, data JSON), workflow_tasks (one per visit to a task step: status, form entry, completed by, due date) with workflow_task_assignees (user morphs, roles, permissions) so WorkflowTask::assignedTo($user) is a query. workflow_history is the audit trail: started, entered, transitioned, action run/failed, completed, cancelled, each with actor morph, from/to step, transition, comment and data. Events: WorkflowStarted, StepEntered, TaskCreated, TransitionTaken, ActionFailed, WorkflowCompleted, WorkflowCancelled. The engine runs in a DB transaction and guards against automatic-step loops.
    • Assignees. Built-in types starter, user, role, permission, subject (an attribute/relation of the subject), plus resolvers apps register (Workflows::assignee('department_head', fn (WorkflowInstance $instance) => …)). Nothing assumes App\Models\User: users are morphs.
    • Actions (Developer-defined actions #5). Javaabu\Workflows\Actions\Action classes in a registry (Workflows::registerAction(), config('workflows.actions')): key, name, icon, builder settings (stencil fields + rules), handle(ActionContext), describe() for the tester. Actions run on entering a step or taking a transition; ones implementing ShouldQueue run as a job. Built-ins: notify (javaabu/notifications BaseNotification when installed, else a plain Laravel notification), update_model (set subject attributes, with {{ data.days }} placeholders), webhook (Laravel's HTTP client, optional signing secret), set_data.
    • Conditions (Conditionals #6). On transitions (and so decision/action steps): rules {field, operator, value} over data.*, subject.* and starter.*, operators =, !=, >, >=, <, <=, in, not_in, contains, filled, blank, combined with all/any. A person only sees transitions whose conditions hold. Fluent: Transition::make('long')->from('check')->to('hr')->when('days', '>', 5).
    • Data (Workflow data with javaabu/form-builder #7). Start and task steps can name a form-builder form (key, optionally pinned to a version). Taking a transition validates and stores a FormEntry through FormBuilder::submit() with the task as subject; answers merge into the instance's data, which conditions and actions read. A transition can skip the form (e.g. "Reject").
    • Fluent definitions and seeding (Fluent definitions and seeding #3). Workflows::define('leave-request')->name()->for(LeaveRequest::class)->steps([Step::start(...), Step::task(...)->assignTo(Assignee::role('manager'))->form('leave-review'), …])->transitions([...])->sync(), idempotent (new version only when the definition changes), lockable to code.
    • Builder (Visual drag-and-drop builder #4). A React WorkflowBuilder with its own small SVG node-graph canvas: Onu has no graph/canvas component (checked ../onu/docs), and React Flow doesn't support connecting by keyboard, is heavy for a <script> bundle and needs workarounds in jsdom. Nodes are focusable buttons: arrow keys move them, Enter opens the inspector, Delete removes; edges are added by dragging from a node's handle or from the inspector ("Add a transition to…"), so everything works by keyboard; moves are announced to screen readers; positions use logical coordinates so it mirrors in RTL. The inspector edits steps, transitions, assignees, forms, actions and conditions; saving PUTs the definition, validated on the server. Inertia renders the component in the page; Blade mounts the same component from a prebuilt workflows::builder entry on [data-workflow-builder].
    • Tester (Workflow tester #8). Workflows::simulate($version, data: [...], choices: ['review' => 'approve']) walks the graph without saving or running actions and returns the path, each step's decision and the actions that would run (describe()), stopping at a task without a choice with its options. A tester page (Blade + React) highlights the path on the graph.
    • HTTP (Blade (javaabu/forms) and Inertia React (@javaabu/forms) views #13, Overridable controllers, policies and default permissions (RBAC, multi-auth) #14). Route::workflows([...]) (admin: list, create/settings, builder, publish/discard, versions, tester, instances) and Route::workflowTasks([...]) (my tasks, an instance, starting a workflow, taking a transition), each with guard/prefix/name/middleware options and controllers from config. WorkflowPolicy, WorkflowInstancePolicy and permissions view_workflows, edit_workflows, delete_workflows, publish_workflows, start_workflows, view_workflow_instances, cancel_workflow_instances through javaabu/permissions; acting on a task is authorized by being an assignee. workflows.views chooses blade or inertia; every route also answers JSON; settings forms are stencil.
    • Optional packages. javaabu/translatable (Optional javaabu/translatable support #15): workflow names and step/transition labels as locale maps, via a Support\Translations like form-builder's. laravel/ai (AI assistant to build workflows (optional) #9): WorkflowAssistant returns the definition JSON, validated with one retry, saved as a draft; tests use laravel/ai's fakes. javaabu/notifications: optional, for the notify action. javaabu/activitylog: optional; workflow_history is the audit trail, and with activitylog installed the events are also logged there.

    Public API sketch

    Workflows::define('leave-request')->…->sync();
    $instance = Workflows::start('leave-request', subject: $leave, by: $user, data: $request->all());
    $instance->currentTasks(); $instance->availableTransitions($user);
    Workflows::transition($instance, 'approve', by: $user, data: [...], comment: 'ރަނގަޅު');
    $instance->history; $instance->cancel($user, 'reason');
    WorkflowTask::assignedTo($user)->open()->get();
    Workflows::simulate('leave-request', data: ['days' => 7], choices: ['review' => 'approve']);
    $leave->workflowInstance('leave-request');    // HasWorkflows trait on subjects

    Order of work

    #1 foundations → #2 engine (definitions, validator, instances, tasks, history, events) with #6 conditions → #3 fluent + seeding → #5 actions → #7 form-builder data → #8 tester (PHP) → #14 routes/controllers/policies → #13 Blade + Inertia pages → #4 builder → #8 tester page → #15 translatable → #9 AI → #12 Dhivehi/RTL → #10 docs → #11 Boost.

    Open questions (I'll decide and record as I go)

    • Parallel branches (fork/join): out of scope for v1 unless cheap; one current step per instance.
    • Due dates/escalations: tasks get an optional due_at from the step (due_in); scheduled escalation is left to apps through events.
  3. n4ff4h commented on Nov 6, 2026

    @n4ff4h
    CollaboratorAuthor

    Final summary

    javaabu/workflows v1.0 is on main and CI is green on every job:

    • the full matrix: PHP 8.4/8.5 × Laravel 12/13 × lowest/stable
    • Pint
    • a leg without Inertia, javaabu/notifications, javaabu/translatable and laravel/ai
    • the JS job: lint, format, typecheck, Vitest with coverage, the examples build and the dist freshness check

    That's 136 PHPUnit tests and 35 Vitest tests. Every next-gen issue is closed except #16 (needs a human).

    What was built

    • Definitions and versions. Steps are start, task, decision, action and end. Transitions carry labels, colours, comment rules, skip-form, conditions and actions. Each version stores one JSON document. Drafts are edited; published versions are immutable, and instances keep the version they started on. DefinitionValidator reports problems keyed by step or transition: keys, types, one start and at least one end, a way out of and a path to an end from every step, one default per automatic step, forms, actions and their settings, and conditions. It also lists unreachable steps.

    • Fluent definitions and seeding. Workflows::define()->for()->steps()->transitions()->sync() is idempotent, can leave the new version as a draft, and can be locked to code.

    • Engine. start, transition, cancel, automatic steps and a loop guard, all in one DB transaction with the instance row locked.

      • Tasks have due times, and assignees can be users (morphs, any guard), roles, permissions, the starter, the subject's relations, an earlier step's actor, or app-defined types (Workflows::assignee()). WorkflowTask::assignedTo($user) is the inbox.
      • Every instance keeps a full history, and seven events fire after commit.
    • Conditions over data, subject.* and starter.*, with all/any matching and 11 operators. They choose the way at decisions and decide which buttons people see.

    • Actions. notify, update_model, set_data and webhook (queued, HMAC-signed), plus custom Action classes with builder settings, validation and describe()/simulate() for the tester. Failures are recorded, or undo the transition when actions_on_failure is throw.

    • Step data with form-builder. The start step and tasks can name a form, optionally pinned to a version. It's validated with FormBuilder::submit(), the entry is stored against the instance or the task, and the answers merge into the data.

    • Tester. Workflows::simulate() takes sample data, a sample subject and per-visit choices. The tester page (Blade and React) shows the path and why each way was taken, and highlights it on the diagram.

    • Builder. WorkflowBuilder is a custom SVG node graph:

      • steps are focusable; arrow keys move them, Enter selects and Delete removes;
      • steps connect by dragging a handle or from the inspector, which edits everything;
      • there's a list view, zoom, Ctrl+S and screen-reader announcements;
      • positions are logical, so the diagram mirrors in RTL.

      Inertia renders it, and the Blade page mounts it from the prebuilt workflows::builder entry (React bundled in). I didn't use a library: Onu has no graph component, and React Flow can't connect by keyboard and is heavy for a Blade bundle.

    • HTTP.

      • Route::workflows() serves the list, settings (a stencil form), the builder, saving drafts as JSON, publish, discard, versions, export, the tester and instances.
      • Route::workflowTasks() serves my tasks, started by me, starting a workflow (with ?subject=), an instance, transitions and cancelling.
      • Every page is in Blade and Inertia React, and every route also answers JSON. Controllers can be replaced from config or per route group.
    • Authorization. WorkflowPolicy and WorkflowInstancePolicy check seven permissions through javaabu/permissions. Assignees act without permissions; manage_workflow_instances can act on any task. Tests cover two guards.

    • Optional packages.

      • javaabu/translatable: names in every language and per-locale builder inputs.
      • laravel/ai: an assistant that writes or changes a draft, validated with one retry. Tests use laravel/ai's fakes.
      • javaabu/notifications: the notify action sends a BaseNotification; without it, a plain Laravel notification.
    • Dhivehi and RTL. Every string is in lang/dv.json, and LangTest checks Thaana, placeholders and logical CSS.

    • Docs and tooling. Docs (30 pages), llms.txt, the Boost guideline and skill, examples, and AGENTS.md.

    • Manifest follow-up (manifest:check asks Blade apps to install the React/Inertia dependencies of package pages manifest#17). The Blade entry is a react() asset. @javaabu/forms, Onu and Inertia are declared for: 'pages', so Blade-only apps' manifest:check doesn't ask for them; FrontendAssetsTest covers this.

    Decisions

    • javaabu/activitylog is not integrated. workflow_history is the audit trail, and the events are there for apps that also want activitylog entries. Adding it would pull exports and filters into dev for little gain.
    • javaabu/notifications is optional (in suggest), not required. It pulls in sms-notifications, and a plain Laravel notification is a fine default.
    • Drafts save even when incomplete. The builder lists the problems; publishing (and sync()) requires a valid definition.

    Intentionally out of scope for v1

    • Parallel branches (fork/join): an instance has one current step.
    • Escalating overdue tasks: tasks have due_at and an overdue() scope; escalation is left to apps through events and a scheduler.
    • A JSON import page: WorkflowDefinition::fromArray()->sync() and the export route cover moving workflows between apps.

    For the user

    • Codecov. Uploads run with fail_ci_if_error: false; make CODECOV_TOKEN available to this repo so they're accepted.
    • Docs registration. Register the package with docs.javaabu.com (Register with Javaabu Docs (docs.javaabu.com) #16).
    • Dhivehi review. The terms in docs/advanced-usage/dhivehi-and-rtl.md#for-review need a Dhivehi speaker, especially "workflow", "transition" and "instance".
    • Release. Tag and submit to Packagist. Once the dependencies are released, drop the VCS repositories in composer.json; once @javaabu/forms is on npm, change its npm dependency in Assets and package.json. pnpm is pinned to 10.28.
    • Not checked in a real browser. The builder and pages are tested in jsdom and the examples build (pnpm examples), but I haven't tried dragging or the styling in a real browser.
  4. n4ff4h commented on Nov 6, 2026

    @n4ff4h
    CollaboratorAuthor

    Follow-up from the orchestrator (Javaabu/manifest#17, Blade skeleton and laravel-skeleton-react reports), in 268d491. CI is green.

    • Blade entry: it's now a plain script() (resources/js/blade/builder.ts, using createElement instead of JSX), not a react() asset. manifest infers React build requirements from react() and from .tsx files. Blade apps load the prebuilt file (React bundled), so manifest:check --scope=assets no longer asks them for react, react-dom or @vitejs/plugin-react. Those are now declared for: 'pages', together with @javaabu/forms, Onu and Inertia. Apps that have pages are asked for them, and so are apps that build the entry with their own Vite (--scope=pages). The requirements page documents how to leave the entry out of the app's build with manifest({ input: false }) and inputs(). Tests cover both scopes.
    • CSS on Inertia: every Inertia page and the @javaabu/workflows index import workflows.css, so Vite bundles it and apps don't add it by hand. The Blade entry imports no CSS, so Blade pages don't load it twice.
    • CSS on Blade: every Blade page now loads workflows::styles, because the page header requires it. Before, only the builder and tester pages loaded it.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    epicTracks a whole packagenext-genNext-gen Javaabu packages

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions