You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat: §4.9 reduced-motion flag + §4.10 thread-model docs
§4.9 — TERMFLOW_REDUCED_MOTION env var (and HOCON
termflow.accessibility.reduced-motion) flow through
RuntimeCtx.config.accessibility.reducedMotion. Spinner accepts a
reducedMotion: Boolean parameter that pins it to frames(0). New
docs/guide/accessibility.md page covers the colour, motion, and
predictable-structure stance. Closes#139.
§4.10 — docs/reference/thread-model.md diagrams the runtime topology
(runtime thread + InputKey producer + Sub.Every scheduler + FCmd
executor + resize listener + shutdown hook) and documents the
invariants apps can rely on. Closes#134.
Roadmap: §4.9 / §4.10 marked landed; only §4.2 (release-doc sweep)
and §4.5 (rolling-console recipe) remain in §4.
|**InputKey producer**| Lazy: started by the first `RuntimeCtx.registerSub` of a `Sub.InputKey`| Reads keystrokes off the `TerminalBackend.reader` and publishes `Cmd.GCmd` per parsed key. |
49
+
|**Sub.Every scheduler**| Lazy: per-`Sub.Every`, started on `registerSub`| A `ScheduledExecutorService` ticks at the configured period and publishes a `Cmd.GCmd` per tick. |
50
+
|**FCmd Future executor**| Per `Cmd.FCmd` — uses `ExecutionContext.global` by default | Runs the user's `Future` body off the runtime thread; the continuation publishes a result `Cmd` back. |
51
+
|**Resize listener**| Lazy on `Sub.TerminalResize`| JLine's SIGWINCH callback runs on its own thread; the listener publishes a `Cmd.GCmd` per resize. |
52
+
|**JVM shutdown hook**| Registered once by `TuiRuntime.run`| Restores cursor / leaves alt-buffer / disables mouse on abrupt exit. |
53
+
54
+
## Invariants you can rely on
55
+
56
+
1.**`update` and `view` always run on the runtime thread.** No two
57
+
`update` calls overlap — the bus is a single consumer. Mutating
58
+
`Model` inside `update` is therefore safe by construction (and the
59
+
model should be immutable anyway).
60
+
2.**`Cmd.FCmd` continuations come back through the bus.** When your
61
+
`Future[A]` completes, the result mapper runs *off* the runtime
62
+
thread, but the produced `Cmd` is published — so the `update` that
63
+
sees it runs on the runtime thread again. You don't need to
64
+
synchronise on shared state, only on whatever the `Future`'s body
65
+
itself touches.
66
+
3.**`Sub` callbacks run off their respective threads.** A `Sub.Every`
67
+
tick fires on the scheduler thread, an `InputKey` parse fires on
68
+
the producer thread. Whatever you do in the callback that produces a
69
+
`Cmd` runs off-main, but the resulting `Cmd` always arrives at
70
+
`update` on the runtime thread.
71
+
4.**Subscriptions start lazily.**`Sub.InputKey`, `Sub.Every`, and
72
+
`Sub.TerminalResize` don't spawn threads or schedule timers until
73
+
`RuntimeCtx.registerSub` is called. Tests using `TestRuntimeCtx`
74
+
keep them dormant deliberately.
75
+
5.**`CmdBus` is a serialising queue.** Multiple producer threads can
76
+
`publish` concurrently; the runtime's `take` / `poll` is the single
77
+
consumer. Order across producers is FIFO by enqueue time.
78
+
79
+
## Gotchas
80
+
81
+
-**Don't block the runtime thread.** Anything synchronous inside
82
+
`update` blocks the entire frame. Use `Cmd.FCmd` (or `Cmd.asyncResult`)
83
+
for I/O.
84
+
-**Don't close over mutable state inside `FCmd`.** The body runs
85
+
off-main, possibly concurrently with another `FCmd` body, and the
86
+
continuation also runs off-main before the resulting `Cmd` is
87
+
published. Pass the data you need by value into the `Future`.
88
+
-**`Sub.Every` tick drift is not corrected.** The scheduler uses
89
+
`scheduleAtFixedRate`, which can drift if the runtime is slow to
90
+
drain ticks. If exact wall-clock cadence matters, use
91
+
`Sub.Every` for *triggering* and read `System.currentTimeMillis()`
92
+
inside `update` for the *timestamp*.
93
+
-**JLine callbacks (resize, signals) are not on a TermFlow-managed
94
+
thread.** Don't do anything in those callbacks except publish to
95
+
`CmdBus`.
96
+
97
+
## Subscription cleanup
98
+
99
+
`CmdBus.cancelAllSubscriptions()` is called by the runtime on exit
100
+
(both clean exit via `Cmd.Exit` and abrupt exit via the shutdown hook).
101
+
Each `Sub`'s `cancel()` is best-effort: it may interrupt the producer
102
+
thread, shut down a scheduler, or close a `Reader`. If one cancel
103
+
throws, the rest still run.
104
+
105
+
## Testing
106
+
107
+
For deterministic tests, use the testkit:
108
+
109
+
-`TestRuntimeCtx` keeps subs dormant — `registerSub` does not call
110
+
`start()`.
111
+
-`TuiTestDriver` advances the model by feeding `Msg`s directly,
112
+
bypassing the bus and threading concerns.
113
+
-`KeySim` and `MouseSim` synthesise input events.
114
+
115
+
Real-time behaviour (`Sub.Every` cadence, JLine reader latency) is
116
+
deliberately *not* covered by the testkit — that's what the sample apps
0 commit comments