Softanza Reactive Objects: A Complete Beginner's Tutorial
Imagine you're using a spreadsheet like Excel. When you change a number in one cell, all the connected formulas update instantly without you having to do anything extra. That's the magic of reactive programming! In simple terms, reactive systems make your code "smart" so that when data changes, everything that depends on it updates automatically—like a chain reaction.
by Mansour Ayouni · in the repository since 2025-08-21 · tutorial · area Concurrency & distribution
Introduction to Reactive Systems
Let's compare traditional programming (where you have to manually handle every update) with reactive programming:
Traditional Programming: Reactive Programming:
[Change Data] ──→ [Manual Updates] [Change Data] ──→ [Automatic Cascading Updates]
↓ ↓
[Update UI] [UI Updates Automatically]
↓ ↓
[Update DB] [DB Updates Automatically]
↓ ↓
[Log Changes] [Logging Happens Automatically]
In reactive systems, you tell the code once: "Hey, whenever this data changes, do these things." Then, the system takes care of the rest. This saves time, reduces bugs, and makes your programs feel more alive and responsive.
The Softanza Reactive Programming System
Softanza is a user-friendly framework built for the Haro programming language. It helps you create reactive programs easily. Think of it as a toolbox with different tools that work together to handle changes in data, files, timers, and more.
Here's a visual overview of the Softanza system:
┌──────────────────────────────────────────────────────────────────────────────┐ │ SOFTANZA REACTIVE SYSTEM │ ├──────────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐ │ │ │ REACTIVE OBJECTS │ │ REACTIVE FUNCTIONS │ │ REACTIVE FILES │ │ │ │ │ │ │ │ │ │ │ │ • Attributes │ │ • Auto-execute │ │ • Watch Changes │ │ │ │ • Watchers │ │ • Dependencies │ │ • Auto-load │ │ │ │ • Computed │ │ • Memoization │ │ │ │ │ │ • Binding │ │ │ │ │ │ │ └─────────────────────┘ └─────────────────────┘ └─────────────────────┘ │ │ │ │ ┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐ │ │ │ REACTIVE STREAMS │ │ REACTIVE TIMERS │ │ REACTIVE HTTP │ │ │ │ │ │ │ │ │ │ │ │ • Data Flows │ │ • Schedules │ │ • Requests │ │ │ │ • Transformations │ │ • Intervals │ │ • Responses │ │ │ │ • Filtering │ │ • Timeouts │ │ • Real-time │ │ │ │ │ │ │ │ │ │ │ └─────────────────────┘ └─────────────────────┘ └─────────────────────┘ │ │ │ ├──────────────────────────────────────────────────────────────────────────────┤ │ libuv Foundation │ │ (Same as used in Node.js and Ring.js) │ └──────────────────────────────────────────────────────────────────────────────┘
This tutorial zooms in on Reactive Objects, the building blocks of Softanza. We'll cover the other parts (like Reactive Functions or Streams) in future guides. For now, let's focus on making objects "reactive" so they respond to changes effortlessly.
Reactive Objects: Core Concepts
A Reactive Object is like a regular object but with superpowers. It notices when its properties (called attributes) change and alerts anything watching them. It's perfect for apps where data updates need to trigger actions, like updating a user's profile.
Basic Structure:
┌────────────────────────────────────────────────────┐ │ REACTIVE OBJECT │ ├────────────────────────────────────────────────────┤ │ Attributes (Data): │ │ • @Name = "John" │ │ • @Age = 25 │ │ • @Email = "john@test.com" │ ├────────────────────────────────────────────────────┤ │ Watchers (Actions on Change): │ │ • Watch(@Name) → Update UI │ │ • Watch(@Age) → Check if Adult │ │ • Watch(@Email) → Send Welcome Email │ ├────────────────────────────────────────────────────┤ │ Computed Properties (Auto-Calculated): │ │ • @FullName ← @FirstName + " " + @LastName │ │ • @IsAdult ← @Age >= 18 │ └────────────────────────────────────────────────────┘
Attributes start with @ to show they're reactive. Watchers are like guards that react when something changes.
Basic Attribute Watching
Watching attributes is the starting point. It's like setting up a notification: "Tell me when this changes!"
Here's a simple example in Haro code. We'll create a reactive object for a user and watch their name and age.
load "../stzbase.ring"
# Create reactive system (like starting the engine)
Rs = new stzReactive()
# Create reactive object (our user)
oXUser = Rs.ReactiveObject()
oXUser.SetAttribute(:@Name, "") # Start with empty name
oXUser.SetAttribute(:@Age, 0) # Start with age 0
# Watch name changes (like a notification alert)
oXUser.Watch(:@Name, func(attr, oldval, newval) {
? "Name changed from (" + @@(oldval) + ") to (" + @@(newval) + ")"
})
# Watch age changes
oXUser.Watch(:@Age, func(attr, oldval, newval) {
? "Age changed from " + @@(oldval) + " to " + @@(newval)
})
# Test changes with delays (to simulate real-time)
Rs.SetTimeout(100, func {
? "Setting name to 'John'..."
oXUser.SetAttribute(:@Name, "John")
? ""
Rs.SetTimeout(500, func {
? "Setting age to 25..."
oXUser.SetAttribute(:@Age, 25)
? ""
Rs.SetTimeout(500, func {
? "Changing name to 'John Doe'..."
oXUser.SetAttribute(:@Name, "John Doe")
})
})
})
Rs.Start() # Start the reactive system
? NL + "✅ Sample completed."illustration
Output:
Setting name to 'John'...
Name changed from ("") to ("John")
Setting age to 25...
Age changed from 0 to 25
Changing name to 'John Doe'...
Name changed from ("John") to ("John Doe")
✅ Sample completed.
Key Points:
- Anonymous Functions: The
funcis a small, unnamed function that runs when the attribute changes. It gets the attribute name, old value, and new value automatically. - SetTimeout: This adds delays to mimic real-world timing, like user inputs happening over time.
- Attribute Naming: The
@prefix marks reactive attributes – it's a Softanza convention. - Why This is Cool: No manual checks! The system watches for you.
Computed Attributes
Computed attributes are like smart formulas. They recalculate automatically when their "ingredients" (dependencies) change. Like in a spreadsheet: Change a number, and the sum updates.
Example: Full name from first and last name, or checking if someone is an adult based on age.
# Create reactive system
Rs = new stzReactive()
# Create user object with attributes
oXUser = Rs.ReactiveObject()
oXUser {
@(:@FirstName = "")
@(:@LastName = "")
@(:@FullName = "")
@(:@Email = "")
@(:@Age = 0)
@(:@IsAdult = false)
}
# Computed: FullName from FirstName + LastName
oXUser.Computed(:@FullName,
func oSelf {
return trim(oSelf.GetAttribute(:@FirstName) + " " + oSelf.GetAttribute(:@LastName))
},
[ :@FirstName, :@LastName ]
)
# Computed: IsAdult from Age
oXUser.Computed(:@IsAdult,
func oSelf {
return oSelf.GetAttribute(:@Age) >= 18
},
[ :@Age ]
)
# Watch computed changes
oXUser.Watch(:@FullName,
func(oSelf, attr, oldval, newval) {
? "Full name computed: (" + newval + ")"
}
)
oXUser.Watch(:@IsAdult,
func(oSelf, attr, oldval, newval) {
? "Adult status: " + newval
}
)
# Test with delays
Rs.SetTimeout(100, func {
? "Setting firstName to 'Jane'..."
oXUser.SetAttribute(:@FirstName, "Jane")
? ""
Rs.SetTimeout(500, func {
? "Setting lastName to 'Smith'..."
oXUser.SetAttribute(:@LastName, "Smith")
? ""
Rs.SetTimeout(500, func {
? "Setting age to 17..."
oXUser.SetAttribute(:@Age, 17)
? ""
Rs.SetTimeout(500, func {
? "Setting age to 21..."
oXUser.SetAttribute(:@Age, 21)
})
})
})
})
Rs.Start()
? NL + "✅ Sample completed."illustration
Output:
Setting firstName to 'Jane'... Full name computed: (Jane) Setting lastName to 'Smith'... Full name computed: (Jane Smith) Setting age to 17... Setting age to 21... Adult status: 1 ✅ Sample completed.
Computed Attributes Flow Diagram:
@FirstName = "Jane" ──┐
├──→ @FullName = "Jane Smith" ──→ (Watcher: Print Update)
@LastName = "Smith" ──┘
@Age = 21 ──→ @IsAdult = true ──→ (Watcher: Print Status)
You define the formula once, and Softanza handles updates.
Attribute Binding
Binding links attributes between objects. Change one, and the others sync automatically – like mirroring data.
Example: A temperature sensor (source) updating multiple displays (targets).
# Create reactive system
Rs = new stzReactive()
# Source object (sensor)
oXSource = Rs.ReactiveObject()
oXSource {
SetAttribute(:@Temperature, 20)
SetAttribute(:@Status, "normal")
}
# Target objects (displays)
oXDisplay1 = Rs.ReactiveObject()
oXDisplay1 {
SetAttribute(:@Temp, 0)
SetAttribute(:@DisplayName, "Display1")
}
oXDisplay2 = Rs.ReactiveObject()
oXDisplay2 {
SetAttribute(:@Temp, 0)
SetAttribute(:@DisplayName, "Display2")
}
# Watch targets
oXDisplay1.Watch(:@Temp, func(attr, oldval, newval) {
cDisplayName = oXDisplay1.GetAttribute(:@DisplayName)
? cDisplayName + " received temperature: " + newval + "°C"
})
oXDisplay2.Watch(:@Temp, func(attr, oldval, newval) {
cDisplayName = oXDisplay2.GetAttribute(:@DisplayName)
? cDisplayName + " received temperature: " + newval + "°C"
})
# Bind source to targets
Rs.BindObjects(oXSource, :@Temperature, oXDisplay1, :@Temp)
Rs.BindObjects(oXSource, :@Temperature, oXDisplay2, :@Temp)
# Test updates
Rs.SetTimeout(100, func {
? "Setting source temperature to 25°C..."
oXSource.SetAttribute(:@Temperature, 25)
Rs.SetTimeout(500, func {
? "Setting source temperature to 30°C..."
oXSource.SetAttribute(:@Temperature, 30)
Rs.SetTimeout(500, func {
? "Setting source temperature to 35°C..."
oXSource.SetAttribute(:@Temperature, 35)
})
})
})
Rs.Start()
? NL + "✅ Sample completed."illustration
Output:
Setting source temperature to 25°C... Display1 received temperature: 25°C Display2 received temperature: 25°C Setting source temperature to 30°C... Display1 received temperature: 30°C Display2 received temperature: 30°C Setting source temperature to 35°C... Display1 received temperature: 35°C Display2 received temperature: 35°C ✅ Sample completed.
Binding Flow Diagram:
┌──────────────────────┐
│ Source Object │
│ @Temperature = 30 │
└──────────┬───────────┘
│
Automatic Binding
│
┌─────────▼─────────┐ ┌─────────────────┐
│ Display1 │ │ Display2 │
│ @Temp = 30 │ │ @Temp = 30 │
└───────────────────┘ └─────────────────┘
Great for syncing data across parts of your app!
Batch Updates
Batch updates group changes together so watchers only trigger once at the end. This avoids unnecessary intermediate updates, like saving a form only after all fields are filled.
Example: Updating a product with multiple attributes.
# Create reactive system
Rs = new stzReactive()
# Create product object
oXProduct = Rs.ReactiveObject()
oXProduct.SetAttribute(:@Name, "")
oXProduct.SetAttribute(:@Price, 0)
oXProduct.SetAttribute(:@Category, "")
oXProduct.SetAttribute(:@InStock, false)
# Watch attributes
oXProduct.Watch(:@Name, func(attr, oldval, newval) {
? " Name updated: " + newval
})
oXProduct.Watch(:@Price, func(attr, oldval, newval) {
? " Price updated: $" + string(newval)
})
oXProduct.Watch(:@Category, func(attr, oldval, newval) {
? " Category updated: " + newval
})
oXProduct.Watch(:@InStock, func(attr, oldval, newval) {
? " Stock status: " + string(newval)
})
Rs.SetTimeout(100, func {
? "Individual updates (watch each change):"
oXProduct.SetAttribute(:@Name, "Laptop")
sleep(0.1)
oXProduct.SetAttribute(:@Price, 999.99)
sleep(0.1)
oXProduct.SetAttribute(:@Category, "Electronics")
sleep(0.1)
oXProduct.SetAttribute(:@InStock, true)
Rs.SetTimeout(1000, func {
? ""
? "Batch updates (all changes processed together):"
oXProduct.Batch(func {
oXProduct.SetAttribute(:@Name, "Gaming Laptop")
oXProduct.SetAttribute(:@Price, 1299.99)
oXProduct.SetAttribute(:@Category, "Gaming")
oXProduct.SetAttribute(:@InStock, false)
})
})
})
Rs.Start()
? NL + "✅ Sample completed."illustration
Output:
Individual updates (watch each change): Name updated: Laptop Price updated: $999.99 Category updated: Electronics Stock status: 1 Batch updates (all changes processed together): Name updated: Gaming Laptop Price updated: $1299.99 Category updated: Gaming Stock status: 0 ✅ Sample completed.
Batch vs Individual Updates:
Individual Updates: Batch Updates: Change 1 ──→ Notify ──→ Update Change 1 ┐ Change 2 ──→ Notify ──→ Update Change 2 ├──→ Single Notify ──→ Update Change 3 ──→ Notify ──→ Update Change 3 ┘
Batching is efficient for grouped changes.
Attribute Streams
Streams treat attribute changes like a flowing river of data. You can transform, filter, or react to the flow.
Example: Processing sensor readings, alerting only for high values.
# Create reactive system
Rs = new stzReactive()
# Create sensor object
oXSensor = Rs.ReactiveObject()
oXSensor.SetAttribute(:@Value, 0)
# Stream from @Value changes
St = oXSensor.StreamAttribute(:@Value)
# Process the stream
St {
Map(func(data) {
# Get newValue from data
newValue = data["newValue"] # Simplified for clarity; use loop if needed
return "Sensor reading: " + string(newValue)
})
Filter(func(message) {
# Only >50
reading = number(substr(message, len("Sensor reading: ") + 1))
return reading > 50
})
OnData(func(message) {
? "🌡️ High reading alert: " + message
})
# Raw data watcher
OnData(func(data) {
? "📊 Raw sensor data: " + string(data["newValue"])
})
}
# Readings to test
anReadings = [10, 25, 60, 75, 30, 85, 45, 95]
nCurrentReading = 1
Rs.SetTimeout(100, func {
NextReading()
})
Rs.Start()
? NL + "✅ Sample completed."
func NextReading()
if nCurrentReading <= len(anReadings)
value = anReadings[nCurrentReading]
? "Setting sensor value to: " + string(value)
oXSensor.SetAttribute(:@Value, value)
nCurrentReading++
if nCurrentReading <= len(anReadings)
Rs.SetTimeout(300, func { NextReading() })
ok
okillustration
Output:
Setting sensor value to: 10 📊 Raw sensor data: 10 Setting sensor value to: 25 📊 Raw sensor data: 25 Setting sensor value to: 60 📊 Raw sensor data: 60 🌡️ High reading alert: Sensor reading: 60 Setting sensor value to: 75 📊 Raw sensor data: 75 🌡️ High reading alert: Sensor reading: 75 Setting sensor value to: 30 📊 Raw sensor data: 30 Setting sensor value to: 85 📊 Raw sensor data: 85 🌡️ High reading alert: Sensor reading: 85 Setting sensor value to: 45 📊 Raw sensor data: 45 Setting sensor value to: 95 📊 Raw sensor data: 95 🌡️ High reading alert: Sensor reading: 95 ✅ Sample completed.
Stream Processing Flow:
Raw Data ──→ Map (Transform) ──→ Filter (>50) ──→ Output 10 ──→ "Reading: 10" ──→ ❌ ──→ (none) 60 ──→ "Reading: 60" ──→ ✅ ──→ High Alert
Streams are powerful for handling ongoing data changes.
Debounced Attributes
Debouncing waits a bit before reacting, useful for rapid changes like typing in a search box. It prevents spamming actions (e.g., API calls) for every keystroke.
Example: Search query that only searches after typing stops.
# Create reactive system
Rs = new stzReactive()
# Create search object
oXSearch = Rs.ReactiveObject()
oXSearch.SetAttribute(:@Query, "")
# Immediate watch (fires on every change)
oXSearch.Watch(:@Query, func(attr, oldval, newval) {
? "🔍 Search query changed: " + @@(newval)
})
# Debounced (waits 800ms)
oXSearch.DebounceAttribute(:@Query, 800, func(attr, oldval, newval) {
? "🎯 Debounced search executed for: (" + newval + ")"
? " (This simulates an API call)"
})
# Simulate typing
queries = ["h", "he", "hel", "hell", "hello", "hello w", "hello wo", "hello wor", "hello world"]
currentQuery = 1
Rs.SetTimeout(100, func {
? "Simulating rapid typing (debounced search will fire only after typing stops):"
TypeNext()
})
Rs.Start()
? NL + "✅ Sample completed."
func TypeNext()
if currentQuery <= len(queries)
query = queries[currentQuery]
? "⌨️ Typing: " + @@(query)
oXSearch.SetAttribute(:@Query, query)
currentQuery++
if currentQuery <= len(queries)
Rs.SetTimeout(150, func { TypeNext() })
else
Rs.SetTimeout(1500, func { Rs.Stop() })
ok
okillustration
Output:
Simulating rapid typing (debounced search will fire only after typing stops):
⌨️ Typing: 'h'
🔍 Search query changed: 'h'
⌨️ Typing: 'he'
🔍 Search query changed: 'he'
⌨️ Typing: 'hel'
🔍 Search query changed: 'hel'
⌨️ Typing: 'hell'
🔍 Search query changed: 'hell'
⌨️ Typing: 'hello'
🔍 Search query changed: 'hello'
⌨️ Typing: 'hello w'
🔍 Search query changed: 'hello w'
⌨️ Typing: 'hello wo'
🔍 Search query changed: 'hello wo'
⌨️ Typing: 'hello wor'
🔍 Search query changed: 'hello wor'
⌨️ Typing: 'hello world'
🔍 Search query changed: 'hello world'
🎯 Debounced search executed for: (hello world)
(This simulates an API call)
✅ Sample completed.
Debouncing Timeline:
Time: 0ms 150 300 450 600 750 900 1050 1200 ────→ 2000ms Input: h he hel hell hello w wo wor world (pause) Watch: ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ Debounce: ✓ (fires once after 800ms pause)
Perfect for optimizing performance in user inputs.
Making Existing Classes Reactive
You don't need to start from scratch! Softanza can make any regular Haro class reactive with ReactivateObject.
Example: Turning a simple Person class reactive.
# Regular class
class Person
name = ""
age = 0
email = ""
def init(pcName, pnAge)
name = pcName
age = pnAge
# Create instance
oPerson = new Person("Youssef", 28)
# Reactive system
Rs = new stzReactive()
# Make reactive
oXPerson = Rs.ReactivateObject(oPerson)
# Watch attributes
oXPerson.Watch(:name, func(attr, oldval, newval) {
? "✓ Name updated: " + oldval + " → " + newval
})
oXPerson.Watch(:age, func(attr, oldval, newval) {
? "✓ Age updated: " + oldval + " → " + newval
})
oXPerson.Watch(:email, func(attr, oldval, newval) {
? "✓ Email set: " + newval
})
# Test changes
Rs.SetTimeout(100, func {
oXPerson {
@(:name = "John Doe")
@(:age = 26)
@(:email = "john@test.com")
}
Rs.SetTimeout(500, func {
? ""
? "Current person info:"
oXPerson {
? " Name: " + name
? " Age: " + age
? " Email: " + email
}
})
})
Rs.Start()
? NL + "✅ Sample completed."illustration
Output:
✓ Name updated: Youssef → John Doe ✓ Age updated: 28 → 26 ✓ Email set: john@test.com Current person info: Name: John Doe Age: 26 Email: john@test.com ✅ Sample completed.
More Advanced Example - Reactive Bank Account:
# Regular class
class BankAccount
balance = 0
accountNumber = ""
status = "active"
def init(cNumber, nBalance)
accountNumber = cNumber
balance = nBalance
# Create instance
oAccount = new BankAccount("ACC-001", 1000)
# Reactive system
Rs = new stzReactive()
# Make reactive
oXAccount = Rs.ReactivateObject(oAccount)
# Watch with logic
oXAccount.Watch(:balance, func(attr, oldval, newval) {
? "💰 Balance: $" + oldval + " → $" + newval
if newval < 100
? "⚠️ Low balance warning!"
ok
if newval > oldval
? "✅ Deposit detected: +" + (newval - oldval)
else
? "📉 Withdrawal: -" + (oldval - newval)
ok
})
oXAccount.Watch(:status, func(attr, oldval, newval) {
? "📄 Account status: " + oldval + " → " + newval
})
# Test
Rs.SetTimeout(100, func {
? "Processing deposit..."
oXAccount.SetAttribute("balance", 1500)
Rs.SetTimeout(500, func {
? ""
? "Processing withdrawal..."
oXAccount.SetAttribute("balance", 50)
Rs.SetTimeout(500, func {
? ""
? "Freezing account..."
oXAccount.SetAttribute("status", "frozen")
})
})
})
Rs.Start()
? NL + "✅ Sample completed."illustration
Output:
Processing deposit... 💰 Balance: $1000 → $1500 ✅ Deposit detected: +500 Processing withdrawal... 💰 Balance: $1500 → $50 ⚠️ Low balance warning! 📉 Withdrawal: -1450 Freezing account... 📄 Account status: active → frozen ✅ Sample completed.
This shows how to add reactivity to existing code without rewriting everything.
Framework Comparison
Here's how Softanza stacks up against popular JavaScript frameworks. Softanza is easier for Haro users and focuses on simplicity.
| Feature | Softanza | Vue.js | MobX | RxJS | Knockout |
|---|---|---|---|---|---|
| Language | Haro | JavaScript | JavaScript | JavaScript | JavaScript |
| Learning Curve | ⭐⭐ Easy | ⭐⭐⭐ Moderate | ⭐⭐⭐ Moderate | ⭐⭐⭐⭐⭐ Hard | ⭐⭐⭐ Moderate |
| Infrastructure | libuv (Node.js) | Custom | Custom | Custom | Custom |
| Reactive Objects | ✅ Native | ✅ Native | ✅ Native | ❌ Manual | ✅ Native |
| Computed Properties | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes |
| Data Binding | ✅ Two-way | ✅ Two-way | ✅ Two-way | ⚡ One-way | ✅ Two-way |
| Debouncing | ✅ Built-in | ❌ Manual | ❌ Manual | ✅ Built-in | ❌ Manual |
| Batch Updates | ✅ Built-in | ✅ Built-in | ✅ Built-in | ✅ Built-in | ❌ Manual |
| Stream Processing | ✅ Yes | ❌ No | ❌ No | ✅ Advanced | ❌ No |
| Object Conversion | ✅ ReactivateObject | ❌ No | ✅ observable | ❌ No | ❌ No |
Softanza shines in ease of use and built-in features for Haro developers.
Best Practices and Coding Style
To make your Softanza code clean and beginner-friendly:
- Use Clear Names: Prefix reactive attributes with
@for easy spotting. - Keep Watchers Simple: One watcher per specific action – avoid cramming too much logic.
- Batch When Possible: Group related updates to improve performance.
- Test with Delays: Use
SetTimeoutto simulate real-world timing. - Document Dependencies: In computed attributes, list all dependencies clearly.
- Start Small: Begin with basic watching, then add computed, bindings, etc.
- Error Handling: Add checks in watchers for invalid data.
- Reuse Objects: Reactivate existing classes to integrate with old code.
Follow these, and your code will be readable and maintainable!
Conclusion
Congratulations! You've journeyed through the basics of Softanza Reactive Objects, from simple watching to advanced streams and debouncing. Reactive programming with Softanza makes your code more efficient, responsive, and fun to write – especially in Haro.
Remember, the key is thinking in terms of "what happens when..." instead of manual steps. Start small: Try watching an attribute in your next project, then build up to computed properties and bindings.
Softanza's focus on simplicity means you can build powerful apps without the headache. Explore the other Softanza components like streams or timers in future tutorials. Happy coding, and may your data flow smoothly! 🚀
Cite
Mansour Ayouni. "Softanza Reactive Objects: A Complete Beginner's Tutorial". Softanza narrations, 2025. https://mayouni.github.io/stzsite/en/narrations/softanza-reactive-objects-narration.html
Not run: its code touches files, the network, the clock or chance. The article is published as written; its blocks are illustrations. The code of the articles is Haro code. Where an article was written before the language took its present name, the name was changed and nothing else. Genre, area and series read from the file's name, the year from its first commit: derived, until the article carries its own header. The file on GitHub(opens in a new tab)

