anbaric 1.24.0 → 1.26.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.
package/docs/README.md CHANGED
@@ -40,6 +40,7 @@ How to put the features together to build something real.
40
40
  - [Human-in-the-loop](patterns/human-in-the-loop.md) — approvals, forms and hand-offs
41
41
  - [Integrating external systems](patterns/integrating-external-systems.md) — waiting on callbacks and webhooks
42
42
  - [Authorization with actors and roles](patterns/authorization.md) — who is allowed to do what
43
+ - [UX practices](patterns/ux-practices.md) — feedback and polling for an asynchronous UI
43
44
  - [Testing your app](patterns/testing.md) — driving a machine in memory
44
45
 
45
46
  ## API reference
package/docs/api/cli.md CHANGED
@@ -25,9 +25,20 @@ project — they walk up to the nearest `package.json`.
25
25
  | `anbaric app configure` | Create or update `.anbaric/app-config.json` (`name`, `internalPort`). |
26
26
  | `anbaric app deploy` | Deploy the app and wait until it is live (prompts before replacing a running one). |
27
27
  | `anbaric app update` | Deploy, replacing a running app **without** prompting. |
28
- | `anbaric app status <name>` | Show deploy state, liveness, and recent logs. |
29
- | `anbaric app tail <name>` | Stream the app's runtime logs. |
30
- | `anbaric app tear-down <name>` | Stop and remove the app (prompts unless `--yes`). |
28
+ | `anbaric app status [name]` | Show deploy state, liveness, and recent logs. |
29
+ | `anbaric app tail [name]` | Stream the app's runtime logs. |
30
+ | `anbaric app tear-down [name]` | Stop and remove the app (prompts unless `--yes`). |
31
+
32
+ The name is optional, because an `app` command is normally about the app you
33
+ are standing in: the CLI walks up from the working directory to the nearest
34
+ `package.json` and takes the name from that project's
35
+ `.anbaric/app-config.json`, falling back to the package's own `name`. Pass a
36
+ name to act on a different app, or to work from outside a project entirely.
37
+
38
+ ```bash
39
+ cd ~/work/crm && anbaric app tail # the app you are in
40
+ anbaric app status link-media-brief # a different app, from anywhere
41
+ ```
31
42
 
32
43
  ## State machines and jobs
33
44
 
@@ -217,15 +217,20 @@ transition whose predicate holds moves the job to `to`.
217
217
 
218
218
  ```ts
219
219
  to : string
220
- predicate : (job : Job) => boolean
220
+ predicate : (job : Job) => boolean // default: () => true
221
221
 
222
- constructor(to : string, predicate : (job : Job) => boolean)
222
+ constructor(to : string, predicate? : (job : Job) => boolean)
223
223
  ```
224
224
 
225
225
  ```ts
226
226
  new Transition("active", (job) => job.properties.get("welcomeSent") === true)
227
+ new Transition("scoring") // unguarded: the actions run, then the job moves on
227
228
  ```
228
229
 
230
+ The predicate is optional. Omit it when a state's actions simply run and the job
231
+ should move on, rather than inventing a sentinel property for the transition to
232
+ read. Guard a transition only when the move is conditional.
233
+
229
234
  ---
230
235
 
231
236
  ## `Job`
@@ -243,18 +248,26 @@ readonly startedAt : Date
243
248
  readonly startedBy : string
244
249
  readonly lastUpdated : Date
245
250
  readonly killed : boolean
246
- status : string // "active" or "Awaiting input"
251
+ status : string // "active", "Awaiting input" or "Failed"
247
252
  waitingFor? : string
248
253
  awaitMetadata? : WaitForInput // present while parked on an Await
249
254
 
250
255
  namespace Job {
251
- const Status = { ACTIVE: "active", AWAITING_INPUT: "Awaiting input" } as const
256
+ const Status = {
257
+ ACTIVE: "active",
258
+ AWAITING_INPUT: "Awaiting input",
259
+ FAILED: "Failed",
260
+ } as const
252
261
  }
253
262
  ```
254
263
 
255
264
  Check whether a job is parked with `job.status === Job.Status.AWAITING_INPUT`;
256
265
  read what it's waiting for from `job.awaitMetadata`.
257
266
 
267
+ A job whose action threw is `FAILED`, with the reason in its audit trail. It
268
+ stays in its state rather than transitioning, and an update that moves it on
269
+ returns it to `ACTIVE` — so a failure is recoverable, not terminal.
270
+
258
271
  ---
259
272
 
260
273
  ## `PropertyDefinition`
@@ -61,6 +61,34 @@ Each time a job is processed, the machine:
61
61
  Because actions only *propose* changes and the machine *applies* them, a job's
62
62
  data is always schema-valid, whoever wrote it.
63
63
 
64
+ A state's actions run **every time** the job is processed in that state, not
65
+ once on entry. That is deliberate — it lets a predicate be time-based
66
+ (`(job) => Date.now() > retryAfter(job)`) or wait on something external. For
67
+ work that must happen only once, guard it:
68
+
69
+ ```ts
70
+ fetchReport.predicate = (job) => !job.properties.has("report");
71
+ ```
72
+
73
+ ## Failing a job
74
+
75
+ If an action throws, the job is marked **failed** (`Job.Status.FAILED`) and the
76
+ reason is recorded against it in the audit trail. You don't need to catch
77
+ errors yourself to stop a job getting stuck — throwing *is* how you say "this
78
+ job cannot proceed":
79
+
80
+ ```ts
81
+ chargeCard.run = async (job) => {
82
+ const outcome = await payments.charge(job.properties.get("amount"));
83
+ if (!outcome.ok) throw new Error(`Card declined for job ${job.id}: ${outcome.reason}`);
84
+ return new Map([["charged", true]]);
85
+ };
86
+ ```
87
+
88
+ A failed job stays where it is rather than transitioning, but it isn't dead: an
89
+ update that moves it on clears the status, so correcting the data and calling
90
+ `updateJob` retries it.
91
+
64
92
  ## Terminal states
65
93
 
66
94
  Mark the end of a process with `Terminal`, which carries an outcome:
@@ -77,6 +105,21 @@ new Terminal("cancelled", Terminal.Outcome.FAILURE),
77
105
 
78
106
  A job that reaches a terminal state stops and is never processed again.
79
107
 
108
+ ## Moving on unconditionally
109
+
110
+ A transition's predicate is optional. If a state's actions simply run and the
111
+ job should then move on, leave the guard off — there is no need to invent a
112
+ sentinel property for the transition to test:
113
+
114
+ ```ts
115
+ new State("enriching", [lookUpCompany], [new Transition("scoring")]),
116
+ ```
117
+
118
+ Guard a transition when the move is genuinely conditional — branching, or
119
+ waiting for something to become true. If the concern is "what if the action
120
+ fails?", throw from the action instead (see [Failing a job](#failing-a-job));
121
+ you don't need an error property and a guard that reads it.
122
+
80
123
  ## Branching
81
124
 
82
125
  A state can have several transitions; the first satisfied one wins. Put the more
@@ -0,0 +1,62 @@
1
+ # UX practices for Anbaric apps
2
+
3
+ Anbaric apps are asynchronous by nature: submitting a form doesn't finish the
4
+ work, it hands a job to a state machine that then moves on its own. A UI that
5
+ ignores that feels broken even when everything is working. These are the
6
+ practices to follow when you build a human-facing interface on Anbaric.
7
+
8
+ ## Acknowledge a submission immediately
9
+
10
+ **Always give feedback the moment a job update is submitted.** `updateJob` and
11
+ `startJob` return once the change is *stored and queued* — not once the job has
12
+ progressed. If the page sits silent, the person cannot tell whether their click
13
+ registered, and will click again.
14
+
15
+ Say what happened and what is happening next:
16
+
17
+ ```
18
+ ✓ Approval submitted — the job is being processed…
19
+ ```
20
+
21
+ Disable the button while the request is in flight so the same update can't be
22
+ sent twice.
23
+
24
+ ## Then follow the job until it settles
25
+
26
+ After acknowledging, watch the job so the page reflects reality rather than a
27
+ guess. Poll it and re-render as the state changes:
28
+
29
+ - **Poll at most once per second.** Anything faster adds load without telling
30
+ the user anything new; a job that transitions immediately is still only
31
+ observable per processing pass.
32
+ - **Stop when there is nothing left to wait for** — the job reached a terminal
33
+ state, parked on an `Await`, or failed. Don't poll a settled job forever.
34
+ - **Show the state, not just a spinner.** "Awaiting approval", "Charging card",
35
+ "Failed — card declined" tells someone far more than an endless whirl.
36
+ - **Back off or stop after a reasonable period**, and say so, rather than
37
+ spinning indefinitely if nothing changes.
38
+
39
+ A job that has parked on an `Await` is waiting for a *person*, possibly not the
40
+ one at the screen. Say what it is waiting for rather than implying the page is
41
+ still loading.
42
+
43
+ ## Surface failures honestly
44
+
45
+ A job whose action threw is `Job.Status.FAILED`, with the reason in its audit
46
+ trail. Show that the work stopped and why. Silently leaving the last-known state
47
+ on screen turns a failure into a mystery.
48
+
49
+ ## Styling (optional)
50
+
51
+ If the user hasn't asked for a particular look, you may use the **Anbaric design
52
+ system** — design tokens, brand assets and React components:
53
+ <https://github.com/anbaric-ai/anbaric-cloud/tree/main/anbaric-design-system>.
54
+ It isn't published to npm, so copy in `tokens.css` and the components you need
55
+ rather than adding a dependency. If the user asked for something specific —
56
+ Tailwind, MUI, plain CSS, their own kit — use that instead; their choice wins.
57
+
58
+ ## See also
59
+
60
+ - [Awaiting input](../features/awaiting-input.md) — pausing a job for a human
61
+ - [Human-in-the-loop](human-in-the-loop.md) — approvals, forms and hand-offs
62
+ - [Serving a web UI](../features/serving-a-web-ui.md) — putting data on a page
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anbaric",
3
- "version": "1.24.0",
3
+ "version": "1.26.0",
4
4
  "description": "Everything needed to write an Anbaric app: state machines, jobs, document and secret stores, local in-memory implementations and the Anbaric Cloud clients",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -24,9 +24,9 @@
24
24
  "prepublishOnly": "npm run build"
25
25
  },
26
26
  "dependencies": {
27
- "anbaric-impl-cloud": "^1.24.0",
28
- "anbaric-data-store": "^1.24.0",
29
- "anbaric-state-machine": "^1.24.0",
30
- "anbaric-tsapi": "^1.24.0"
27
+ "anbaric-impl-cloud": "^1.26.0",
28
+ "anbaric-data-store": "^1.26.0",
29
+ "anbaric-state-machine": "^1.26.0",
30
+ "anbaric-tsapi": "^1.26.0"
31
31
  }
32
32
  }