💡 Sept. Tip => everything has a meaning
Or how to define the semantics of a data type's values.
During this month of September 2025, we'll highlight various and interesting aspects of libmodulor
.
In libmodulor
, each value is represented with a data-type.
Let's say we are working on a product to manage the machines/systems running in a factory.
We want to visualize each one with their status.
The possible statuses are : down
, running
and unstable
.
In the GUI (or TUI) targets, we want to display these values with a badge with specific colors like so :
+----+----------------+----------------+
| ID | Machine Name | Status |
+----+----------------+----------------+
| 01 | Server-Alpha | [ RUNNING ] |
| 02 | Server-Beta | [ DOWN ] |
| 03 | Server-Gamma | [ UNSTABLE ] |
| 04 | Server-Delta | [ RUNNING ] |
+----+----------------+----------------+
The fact that down
means "bad", running
means "good" and unstable
means "something is wrong" is part of the spec
and should never be defined in the UI layer.
That's why libmodulor
defines the notion of SemanticsVariant
that can be attached to any data type.
This is typically how we would design the TSystemStatus
data type :
export type SystemStatus = 'down' | 'running' | 'unstable';
export class TSystemStatus extends TString<SystemStatus> {
public static readonly OPTIONS: SystemStatus[] = [
'down',
'running',
'unstable',
];
constructor() {
super();
this.setOptions(
TSystemStatus.OPTIONS.map((v) => ({
label: v,
value: v,
})),
{ shouldTranslateLabels: true },
);
this.setSemanticsMapping({
down: { variant: 'danger' },
running: { variant: 'success' },
unstable: { variant: 'warning' },
});
}
}
Now it's up to the UI layer to define that danger
is "red", success
is "green" and warning
is "orange". Or not.
But what if the possible values of the data type are not discrete ?
For example, following up with our initial scenario, we now want to display the uptime of each machine/system.
+----+----------------+----------------+------------+
| ID | Machine Name | Status | Uptime |
+----+----------------+----------------+------------+
| 01 | Server-Alpha | [ RUNNING ] | [ 99.8% ] |
| 02 | Server-Beta | [ DOWN ] | [ 72.4% ] |
| 03 | Server-Gamma | [ UNSTABLE ] | [ 88.1% ] |
| 04 | Server-Delta | [ RUNNING ] | [ 95.6% ] |
+----+----------------+----------------+------------+
Easy : we define a new data type named TSystemUptime
that extends the TPercentage
data type like so :
export type SystemUptime = Percentage;
export class TSystemUptime extends TPercentage {
constructor() {
super();
this.setSemanticsPredicate((v) => {
if (v < 0.95) {
return { variant: 'danger' };
}
if (v < 0.98) {
return { variant: 'warning' };
}
return { variant: 'success' };
});
}
}
With setSemanticsMapping
and setSemanticsPredicate
we can handle all the possible cases.
All in all, the output type of our ListSystemsUCD
would look like this :
export interface ListSystemsOPI0 extends UCOPIBase {
name: FreeTextShort;
status: SystemStatus;
uptime: SystemUptime;
}
Voilà c'est tout.