From eff64aa6963869800c7c9bd99308075d3fe1cb80 Mon Sep 17 00:00:00 2001 From: OpenClaw Bot Date: Sat, 28 Feb 2026 07:17:10 +0800 Subject: [PATCH 1/8] docs: seed 'Wolves in the Open Field' blog post --- .../2026_06_05_Wolves_in_the_Open_Field.md | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/content/drafts/blog/2026_06_05_Wolves_in_the_Open_Field.md diff --git a/src/content/drafts/blog/2026_06_05_Wolves_in_the_Open_Field.md b/src/content/drafts/blog/2026_06_05_Wolves_in_the_Open_Field.md new file mode 100644 index 0000000..6541fde --- /dev/null +++ b/src/content/drafts/blog/2026_06_05_Wolves_in_the_Open_Field.md @@ -0,0 +1,42 @@ +--- +title: "Wolves in the Open Field: The Asymmetry of Structural Mismatch" +description: "Why flat organizations are uniquely vulnerable to hierarchical leaders, and how the 'Wolf' thrives among 'Sheep'." +pubDate: "Jun 05 2026" +tags: ["Leadership", "Culture", "Management", "Organizational Design"] +heroImage: "/assets/blog/2026_06_05_Wolves_in_the_Open_Field/structural_mismatch.png" +--- + +There is no such thing as a perfect organizational structure, but there is definitely such a thing as a catastrophic leadership mismatch. + +In my observations, organizations generally fall into two categories: the **Deep Hierarchy** (driven by layers, politics, and rules) and the **Flat Field** (driven by autonomy, trust, and accountability). Both can work, but they require entirely different types of "Internal Code" in their leaders. + +The problem arises when a leader from one world lands in the other. But the fallout of this mismatch is not symmetrical. + +## The Frustrated Steward + +When a "Flat Leader"—someone who thrives on stewardship, direct accountability, and removing roadblocks—lands in a deeply layered hierarchy, the outcome is predictable: **They leave.** + +The friction of the hierarchy acts as an external immune response. The slow pace, the endless "alignment" meetings, and the rigid rules act as a swamp that drowns the steward's energy. Because the steward values impact over optics, they quickly realize they cannot deliver the "Why" in such a system. They exit early, and the hierarchy remains unchanged. + +## The Wolf among Sheep + +However, when a "Hierarchical Leader"—someone whose primary skill is political maneuvering, optics management, and "managing up"—lands in a flat organization, the outcome is far more dangerous. **They thrive.** + +In a flat organization, people are generally "Sheep" in the best sense: they assume good intent, they value autonomy, and they expect others to be as intellectually honest as they are. This environment is a vacuum of formal power, and to a hierarchical leader, a vacuum is an invitation. + +The Wolf doesn't leave. Instead, they begin to build a **Shadow Hierarchy**. + +## The Colonization of Trust + +Because there are no formal "checkpoints" or rigid layers to stop them, the hierarchical leader uses their political savvy to: +1. **Manufacture Optics:** They ensure that upper management only sees a "Theory of Success" that looks perfect on paper. +2. **Drain the Life Force:** They begin to demand compliance over capability, slowly draining the team’s motivation and "Energy lever." +3. **Entrench:** They are experts at survival. By the time the organization feels the "slap in the face" of a failing culture, the Wolf has already built a web of relationships and "managed" perceptions that make them incredibly difficult to oust. + +## Guarding the Field + +The tragedy of the Wolf is that they are often only identified after the best "Sheep" (the autonomous high-performers) have already left. + +If you are leading a flat organization, your primary job isn't just to set the vision—it is to be the **Guardian of the Field**. You must look past the polished reports and the "political wins" and look for the subtle "smell" of a shadow hierarchy forming. + +True wisdom in a flat organization is knowing that your structure isn't just a design choice; it’s a fragile ecosystem that requires you to spot the Wolf before they've finished building their den. From 43a4dd4b117233574ac6063d124ff9c57dac1cd7 Mon Sep 17 00:00:00 2001 From: julwrites Date: Mon, 2 Mar 2026 08:28:42 +0800 Subject: [PATCH 2/8] feat(mortgage-calculator): implement HDB Savings Simulator mode --- public/labs/mortgage-calculator/app.js | 219 ++++++++++++++++----- public/labs/mortgage-calculator/index.html | 145 +++++++++++--- 2 files changed, 287 insertions(+), 77 deletions(-) diff --git a/public/labs/mortgage-calculator/app.js b/public/labs/mortgage-calculator/app.js index 7a7e9be..62ff3ae 100644 --- a/public/labs/mortgage-calculator/app.js +++ b/public/labs/mortgage-calculator/app.js @@ -24,6 +24,7 @@ document.addEventListener('DOMContentLoaded', () => { let recurringExtraPayments = []; let monthlyPayments = []; let recalcMode = 'reduceTerm'; + let calcAppMode = 'standard'; // 'standard' or 'hdb' // Initialize when DOM is ready initializeCalculator(); @@ -56,28 +57,49 @@ document.addEventListener('DOMContentLoaded', () => { function setupFormListeners() { console.log('Setting up form listeners...'); + // Mode toggle listeners + const modeOptions = document.querySelectorAll('input[name="calcMode"]'); + modeOptions.forEach(option => { + option.addEventListener('change', function (e) { + if (e.target.checked) { + calcAppMode = e.target.value; + document.getElementById('mortgageForm').style.display = calcAppMode === 'standard' ? 'block' : 'none'; + document.getElementById('hdbForm').style.display = calcAppMode === 'hdb' ? 'block' : 'none'; + + const recalcSection = document.querySelector('.recalc-options-compact'); + if (recalcSection) { + recalcSection.style.display = calcAppMode === 'standard' ? 'flex' : 'none'; + } + + updateLoanDataFromForm(); + calculateMortgage(); + } + }); + }); + // Loan parameter inputs - const inputs = ['housingPrice', 'loanAmount', 'loanYears', 'interestRate', 'startMonth', 'startYear']; + const inputs = ['housingPrice', 'loanAmount', 'loanYears', 'interestRate', 'startMonth', 'startYear', + 'hdbLoanBalance', 'hdbMonthlyInstalment', 'hdbInterestRate', 'hdbStartMonth', 'hdbStartYear']; inputs.forEach(inputId => { const element = document.getElementById(inputId); if (element) { - element.addEventListener('input', function() { + element.addEventListener('input', function () { updateLoanDataFromForm(); calculateMortgage(); }); - element.addEventListener('change', function() { + element.addEventListener('change', function () { updateLoanDataFromForm(); calculateMortgage(); }); } }); - + // Recalculation radio buttons - FIX: Add proper event listeners const recalcOptions = document.querySelectorAll('input[name="recalcOption"]'); recalcOptions.forEach(option => { - option.addEventListener('change', function(e) { + option.addEventListener('change', function (e) { if (e.target.checked) { const oldMode = recalcMode; recalcMode = e.target.value; @@ -95,7 +117,7 @@ document.addEventListener('DOMContentLoaded', () => { const addBtn = document.getElementById('addExtraPayment'); if (addBtn) { console.log('Add button found, attaching event listener'); - addBtn.addEventListener('click', function(e) { + addBtn.addEventListener('click', function (e) { e.preventDefault(); console.log('Add button clicked'); addExtraPayment(); @@ -107,7 +129,7 @@ document.addEventListener('DOMContentLoaded', () => { // Enter key in amount field const amountInput = document.getElementById('extraPaymentAmount'); if (amountInput) { - amountInput.addEventListener('keypress', function(e) { + amountInput.addEventListener('keypress', function (e) { if (e.key === 'Enter') { e.preventDefault(); addExtraPayment(); @@ -118,7 +140,7 @@ document.addEventListener('DOMContentLoaded', () => { // Form submission const form = document.getElementById('extraPaymentForm'); if (form) { - form.addEventListener('submit', function(e) { + form.addEventListener('submit', function (e) { e.preventDefault(); addExtraPayment(); }); @@ -128,7 +150,7 @@ document.addEventListener('DOMContentLoaded', () => { function setupRecurringExtraPaymentListeners() { const addBtn = document.getElementById('addRecurringExtraPayment'); if (addBtn) { - addBtn.addEventListener('click', function(e) { + addBtn.addEventListener('click', function (e) { e.preventDefault(); addRecurringExtraPayment(); }); @@ -138,7 +160,7 @@ document.addEventListener('DOMContentLoaded', () => { function setupExportListeners() { const monthlyBtn = document.getElementById('exportMonthlyBtn'); if (monthlyBtn) { - monthlyBtn.addEventListener('click', function(e) { + monthlyBtn.addEventListener('click', function (e) { e.preventDefault(); exportMonthlyPaymentsToCSV(); }); @@ -146,7 +168,7 @@ document.addEventListener('DOMContentLoaded', () => { const extraBtn = document.getElementById('exportExtraBtn'); if (extraBtn) { - extraBtn.addEventListener('click', function(e) { + extraBtn.addEventListener('click', function (e) { e.preventDefault(); exportExtraPaymentsToCSV(); }); @@ -156,17 +178,26 @@ document.addEventListener('DOMContentLoaded', () => { function updateLoanDataFromForm() { console.log('Updating loan data from form...'); - loanData.housingPrice = parseFloat(document.getElementById('housingPrice').value) || 0; - loanData.loanAmount = parseFloat(document.getElementById('loanAmount').value) || 0; - loanData.loanYears = parseInt(document.getElementById('loanYears').value) || 0; - loanData.interestRate = parseFloat(document.getElementById('interestRate').value) || 0; - loanData.startMonth = parseInt(document.getElementById('startMonth').value) || 0; - loanData.startYear = parseInt(document.getElementById('startYear').value) || 2024; - - // Validate loan amount doesn't exceed housing price - if (loanData.loanAmount > loanData.housingPrice && loanData.housingPrice > 0) { - document.getElementById('loanAmount').value = loanData.housingPrice; - loanData.loanAmount = loanData.housingPrice; + if (calcAppMode === 'standard') { + loanData.housingPrice = parseFloat(document.getElementById('housingPrice').value) || 0; + loanData.loanAmount = parseFloat(document.getElementById('loanAmount').value) || 0; + loanData.loanYears = parseInt(document.getElementById('loanYears').value) || 0; + loanData.interestRate = parseFloat(document.getElementById('interestRate').value) || 0; + loanData.startMonth = parseInt(document.getElementById('startMonth').value) || 0; + loanData.startYear = parseInt(document.getElementById('startYear').value) || 2024; + + // Validate loan amount doesn't exceed housing price + if (loanData.loanAmount > loanData.housingPrice && loanData.housingPrice > 0) { + document.getElementById('loanAmount').value = loanData.housingPrice; + loanData.loanAmount = loanData.housingPrice; + } + } else { + loanData.loanAmount = parseFloat(document.getElementById('hdbLoanBalance').value) || 0; + loanData.monthlyPayment = parseFloat(document.getElementById('hdbMonthlyInstalment').value) || 0; + loanData.interestRate = parseFloat(document.getElementById('hdbInterestRate').value) || 0; + loanData.startMonth = parseInt(document.getElementById('hdbStartMonth').value) || 0; + loanData.startYear = parseInt(document.getElementById('hdbStartYear').value) || 2024; + loanData.loanYears = 50; // High ceiling max limit } console.log('Loan data updated:', loanData); @@ -194,23 +225,37 @@ document.addEventListener('DOMContentLoaded', () => { } // Calculate basic monthly payment (original) - loanData.monthlyPayment = calculateMonthlyPayment( - loanData.loanAmount, - loanData.interestRate, - loanData.loanYears - ); + if (calcAppMode === 'standard') { + loanData.monthlyPayment = calculateMonthlyPayment( + loanData.loanAmount, + loanData.interestRate, + loanData.loanYears + ); + } + loanData.recastMonthlyPayment = 0; console.log('Base monthly payment:', loanData.monthlyPayment); // FIX: Generate different amortization schedules based on recalc mode - if (extraPayments.length > 0 || recurringExtraPayments.length > 0) { - if (recalcMode === 'reduceTerm') { - generateAmortizationScheduleReduceTerm(); - } else { - generateAmortizationScheduleReducePayment(); + if (calcAppMode === 'hdb') { + recalcMode = 'reduceTerm'; // HDB reduces term + generateAmortizationScheduleHDB(false); + loanData.originalTotalInterest = loanData.currentTotalInterest; + loanData.originalPayoffDate = loanData.currentPayoffDate; + + if (extraPayments.length > 0 || recurringExtraPayments.length > 0) { + generateAmortizationScheduleHDB(true); } } else { - generateAmortizationScheduleOriginal(); + if (extraPayments.length > 0 || recurringExtraPayments.length > 0) { + if (recalcMode === 'reduceTerm') { + generateAmortizationScheduleReduceTerm(); + } else { + generateAmortizationScheduleReducePayment(); + } + } else { + generateAmortizationScheduleOriginal(); + } } // Update UI @@ -222,6 +267,94 @@ document.addEventListener('DOMContentLoaded', () => { console.log('Mortgage calculation complete'); } + function generateAmortizationScheduleHDB(applyExtra) { + const principal = loanData.loanAmount; + const monthlyRate = loanData.interestRate / 100 / 12; // Standard monthly + const monthlyPayment = loanData.monthlyPayment; + + let remainingBalance = principal; + let totalInterest = 0; + + monthlyPayments = []; + + const startDate = new Date(loanData.startYear, loanData.startMonth, 1); + let paymentNumber = 1; + let currentDate = new Date(startDate); + + const sortedExtraPayments = [...extraPayments].sort((a, b) => a.paymentDate - b.paymentDate); + + while (remainingBalance > 0.01 && paymentNumber < 1000) { + let totalExtraThisMonth = 0; + + if (applyExtra) { + const extraPaymentsThisMonth = sortedExtraPayments.filter(ep => + ep.paymentDate.getFullYear() === currentDate.getFullYear() && + ep.paymentDate.getMonth() === currentDate.getMonth() + ); + + if (extraPaymentsThisMonth.length > 0) { + totalExtraThisMonth = extraPaymentsThisMonth.reduce((sum, ep) => sum + ep.amount, 0); + } + + recurringExtraPayments.forEach(rep => { + const start = new Date(rep.startDate); + const end = new Date(rep.endDate); + if (currentDate >= start && currentDate <= end) { + if (rep.frequency === 'monthly') { + totalExtraThisMonth += rep.amount; + } else if (rep.frequency === 'quarterly' && (currentDate.getMonth() - start.getMonth()) % 3 === 0) { + totalExtraThisMonth += rep.amount; + } else if (rep.frequency === 'annually' && currentDate.getMonth() === start.getMonth()) { + totalExtraThisMonth += rep.amount; + } + } + }); + } + + // HDB specific exact logic + // HDB uses standard Monthly Rest calculation (rate / 12) + const interestPayment = Math.round(remainingBalance * monthlyRate * 100) / 100; + let principalPayment = monthlyPayment - interestPayment; + + principalPayment += totalExtraThisMonth; + + if (principalPayment > remainingBalance) { + principalPayment = remainingBalance; + } + + if ((monthlyPayment + totalExtraThisMonth) > remainingBalance + interestPayment) { + principalPayment = remainingBalance; + } + + remainingBalance -= principalPayment; + totalInterest += interestPayment; + + const payment = { + paymentNumber, + paymentDate: new Date(currentDate), + monthlyPayment: monthlyPayment, + principalPayment: principalPayment, + interestPayment: interestPayment, + remainingBalance: Math.max(0, remainingBalance), + extraPayment: totalExtraThisMonth, + hasExtraPayment: totalExtraThisMonth > 0 + }; + + monthlyPayments.push(payment); + + if (remainingBalance <= 0.01) { + break; + } + + paymentNumber++; + currentDate.setMonth(currentDate.getMonth() + 1); + } + + loanData.currentTotalInterest = totalInterest; + loanData.currentPayoffDate = monthlyPayments.length > 0 ? + monthlyPayments[monthlyPayments.length - 1].paymentDate : null; + } + function generateAmortizationScheduleOriginal() { const principal = loanData.loanAmount; const monthlyRate = loanData.interestRate / 100 / 12; @@ -609,7 +742,7 @@ document.addEventListener('DOMContentLoaded', () => { removeBtn.title = 'Remove this extra payment'; // FIX: Use the original index to ensure proper removal - removeBtn.addEventListener('click', function(e) { + removeBtn.addEventListener('click', function (e) { e.preventDefault(); e.stopPropagation(); console.log('Remove button clicked for index:', payment.originalIndex); @@ -650,7 +783,7 @@ document.addEventListener('DOMContentLoaded', () => { removeBtn.type = 'button'; removeBtn.title = 'Remove this recurring extra payment'; - removeBtn.addEventListener('click', function(e) { + removeBtn.addEventListener('click', function (e) { e.preventDefault(); e.stopPropagation(); removeRecurringExtraPayment(index); @@ -772,11 +905,9 @@ document.addEventListener('DOMContentLoaded', () => { console.log('Removing extra payment at index:', index); if (index >= 0 && index < extraPayments.length) { - if (confirm('Remove this extra payment?')) { - extraPayments.splice(index, 1); - console.log('Extra payment removed. Remaining count:', extraPayments.length); - calculateMortgage(); // This will trigger recalculation and display update - } + extraPayments.splice(index, 1); + console.log('Extra payment removed. Remaining count:', extraPayments.length); + calculateMortgage(); // This will trigger recalculation and display update } else { console.error('Invalid index for removing extra payment:', index); } @@ -784,10 +915,8 @@ document.addEventListener('DOMContentLoaded', () => { function removeRecurringExtraPayment(index) { if (index >= 0 && index < recurringExtraPayments.length) { - if (confirm('Remove this recurring extra payment?')) { - recurringExtraPayments.splice(index, 1); - calculateMortgage(); - } + recurringExtraPayments.splice(index, 1); + calculateMortgage(); } } @@ -904,7 +1033,7 @@ document.addEventListener('DOMContentLoaded', () => { } // Keyboard shortcuts - document.addEventListener('keydown', function(e) { + document.addEventListener('keydown', function (e) { if ((e.ctrlKey || e.metaKey) && e.key === 'm') { e.preventDefault(); exportMonthlyPaymentsToCSV(); diff --git a/public/labs/mortgage-calculator/index.html b/public/labs/mortgage-calculator/index.html index 4d8bd99..24614a8 100644 --- a/public/labs/mortgage-calculator/index.html +++ b/public/labs/mortgage-calculator/index.html @@ -1,5 +1,6 @@ + @@ -7,6 +8,7 @@ +
@@ -14,6 +16,17 @@

Compact Mortgage Calculator

+ +
+ + +
+
@@ -22,22 +35,26 @@

Compact Mortgage Calculator

- +
- +
- +
- +
- +
- +
- +
@@ -57,28 +74,84 @@

Compact Mortgage Calculator

- +
- +
- +
- +
- +
- + +
+ + +
@@ -95,30 +168,30 @@

Compact Mortgage Calculator

$0.00
- +
Total Interest
$0.00
- +
Total Payments
$0.00
- +
Payoff Date
-
- +