weekly updates

This commit is contained in:
2026-03-19 17:59:15 +00:00
parent 980f66d77d
commit d04ff6b68e
39 changed files with 2401 additions and 17448 deletions

View File

@@ -4,7 +4,7 @@ VENV := .venv
PY := $(VENV)/bin/python
PIP := $(VENV)/bin/pip
all: fix-permissions clean-output clean-venv build search
all: fix-permissions clean-output build search clean-venv
build:
@echo "Building project..."

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -77,17 +77,17 @@ function buildCommentTree(comments) {
const byId = {};
const roots = [];
comments.forEach(c => {
c.children = [];
byId[c.id] = c;
comments.forEach(comment => {
comment.children = [];
byId[comment.id] = comment;
});
comments.forEach(c => {
if (c.parent_id) {
const parent = byId[c.parent_id];
if (parent) parent.children.push(c);
comments.forEach(comment => {
if (comment.parent_id) {
const parent = byId[comment.parent_id];
if (parent) parent.children.push(comment);
} else {
roots.push(c);
roots.push(comment);
}
});
@@ -112,7 +112,7 @@ function renderComments(comments) {
}
const tree = buildCommentTree(comments);
tree.forEach(c => list.appendChild(createComment(c)));
tree.forEach(comment => list.appendChild(createComment(comment)));
}
@@ -153,8 +153,8 @@ async function postComment(author, content, parentId = null) {
function wireCommentForm() {
const form = document.getElementById("comment-form");
form.addEventListener("submit", async e => {
e.preventDefault();
form.addEventListener("submit", async event => {
event.preventDefault();
const authorInput = form.querySelector("input[name='author']");
const textarea = form.querySelector("textarea[name='content']");
@@ -167,7 +167,7 @@ function wireCommentForm() {
const ok = await postComment(author, content);
if (ok) {
textarea.value = "";
loadComments(); // same pattern as your Kanban board
loadComments();
} else {
alert("Failed to post comment");
}

View File

@@ -101,33 +101,36 @@ function populateMobileControls(items) {
};
}
document.getElementById("move-confirm").addEventListener("click", async () => {
const itemId = document.getElementById("move-item").value;
const from = document.getElementById("move-from").value;
const to = document.getElementById("move-to").value;
const moveConfirmBtn = document.getElementById("move-confirm");
if (moveConfirmBtn) {
moveConfirmBtn.addEventListener("click", async () => {
const itemId = document.getElementById("move-item").value;
const from = document.getElementById("move-from").value;
const to = document.getElementById("move-to").value;
if (!itemId || !to) {
alert("Select an item and target column");
return;
}
if (!itemId || !to) {
alert("Select an item and target column");
return;
}
if (from === to) {
alert("Item is already in that column");
return;
}
if (from === to) {
alert("Item is already in that column");
return;
}
const res = await fetch(`/api/competencies/items/${itemId}/state`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ state: to }),
const res = await fetch(`/api/competencies/items/${itemId}/state`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ state: to }),
});
if (res.ok) {
loadBoard();
} else {
alert("Failed to move competency");
}
});
if (res.ok) {
loadBoard();
} else {
alert("Failed to move competency");
}
});
}
let draggedItemId = null;

View File

@@ -0,0 +1,557 @@
/* ============================================================
wird-tracker.js
============================================================ */
(function () {
"use strict";
const API = "/api/wird";
// type: "count" | "juz" | "min" | "rating"
const DAILY_WIRD = {
durood: { label: "Durood", type: "count", unit: "count", target: 500 },
istighfar: { label: "Istighfar", type: "count", unit: "count", target: 200 },
quran: { label: "Quran", type: "juz", unit: "juz", target: 3 },
muraqabah: { label: "Muraqabah", type: "min", unit: "min", target: 10 },
wuqoof_qalbi: { label: "Wuquf Qalbi", type: "rating", unit: "", target: 3 },
};
const WIRD_META = {
...DAILY_WIRD,
shaykh_meeting: { label: "Meeting w/ Shaykh", type: "meeting", unit: "meeting", target: 1 },
};
const RATING_LABELS = {
1: "distracted",
2: "scattered",
3: "present",
4: "attentive",
5: "absorbed",
};
const MEETING_CYCLE_DAYS = 21;
const $ = (sel, ctx = document) => ctx.querySelector(sel);
const $$ = (sel, ctx = document) => [...ctx.querySelectorAll(sel)];
const isoDate = (d = new Date()) => d.toISOString().slice(0, 10);
function daysBetween(a, b) {
return Math.round((new Date(b) - new Date(a)) / 86400000);
}
function addDays(iso, n) {
const d = new Date(iso); d.setDate(d.getDate() + n); return isoDate(d);
}
function fmtDisplay(iso) {
return new Date(iso + "T00:00:00").toLocaleDateString("en-GB", {
day: "numeric", month: "short", year: "numeric",
});
}
function fmtValue(type, val) {
const meta = WIRD_META[type];
if (!meta) return val;
if (meta.type === "rating") return val ? `${RATING_LABELS[val] || val} (${val}/5)` : "—";
if (meta.type === "meeting") return val >= 1 ? "✓ attended" : "✗ missed";
return `${Number(val).toLocaleString()} ${meta.unit}`;
}
function statusClass(type, val) {
const meta = WIRD_META[type];
if (!meta || !val || val <= 0) return "";
if (meta.type === "rating") {
if (val >= 4) return "over";
if (val >= 3) return "done";
return "miss";
}
if (val >= meta.target * 1.1) return "over";
if (val >= meta.target) return "done";
return "miss";
}
let allEntries = [];
let todayMap = {};
let meetingLog = [];
let chart = null;
document.addEventListener("DOMContentLoaded", async () => {
setTodayLabel();
await loadAll();
renderToday();
renderMeetingPanel();
renderHistory();
renderChart();
bindControls();
});
function setTodayLabel() {
$("#today-date").textContent = new Date().toLocaleDateString("en-GB", {
weekday: "long", day: "numeric", month: "long", year: "numeric",
});
}
async function loadAll() {
try {
const r = await fetch(`${API}/entries`);
if (!r.ok) throw new Error(r.status);
allEntries = await r.json();
buildTodayMap();
buildMeetingLog();
} catch (e) {
console.error("Wird API error:", e);
}
}
function buildTodayMap() {
const today = isoDate();
todayMap = {};
for (const e of allEntries) {
if (e.date !== today) continue;
// For ratings, take the latest entry (last log wins), not a sum
if (WIRD_META[e.wirdType]?.type === "rating") {
const existing = todayMap[e.wirdType];
if (!existing || e.createdAt > existing.createdAt) {
todayMap[e.wirdType] = Number(e.value);
}
} else {
todayMap[e.wirdType] = (todayMap[e.wirdType] || 0) + Number(e.value);
}
}
}
function buildMeetingLog() {
meetingLog = allEntries
.filter(e => e.wirdType === "shaykh_meeting" && Number(e.value) >= 1)
.sort((a, b) => b.date.localeCompare(a.date));
}
async function postEntry(payload) {
const r = await fetch(`${API}/entries`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
if (!r.ok) throw new Error(await r.text());
return r.json();
}
function renderToday() {
const wrap = $("#wird-cards");
wrap.innerHTML = "";
let done = 0;
const types = Object.keys(DAILY_WIRD);
for (const type of types) {
const meta = DAILY_WIRD[type];
const val = todayMap[type] || 0;
const cls = statusClass(type, val);
if (cls === "done" || cls === "over") done++;
const card = document.createElement("div");
card.className = `wird-card ${cls}`;
card.dataset.type = type;
if (meta.type === "rating") {
card.innerHTML = buildRatingCard(meta, val);
} else {
const pct = Math.min((val / meta.target) * 100, 100);
card.innerHTML = `
<div class="card-name">${meta.label}</div>
<div class="card-meta">
${fmtValue(type, val)}
<span style="opacity:.5"> / ${meta.target.toLocaleString()} ${meta.unit}</span>
</div>
<div class="card-bar-wrap">
<div class="card-bar-fill" style="width:${pct}%"></div>
</div>
<button class="card-log-btn">+ log</button>
`;
}
card.querySelector(".card-log-btn").addEventListener("click", () => openModal(type));
wrap.appendChild(card);
}
const pct = Math.round((done / types.length) * 100);
$("#today-progress-fill").style.width = pct + "%";
$("#today-progress-label").textContent = `${done} / ${types.length} complete`;
}
function buildRatingCard(meta, val) {
// 5 pips, filled up to current rating
const pips = [1,2,3,4,5].map(n => {
const filled = val >= n;
const isMin = n === meta.target;
return `<span class="card-pip ${filled ? "filled" : ""} ${isMin ? "min-marker" : ""}"
title="${RATING_LABELS[n]}"></span>`;
}).join("");
const lbl = val ? `${RATING_LABELS[val]} <span style="opacity:.45">(${val}/5)</span>` : "not logged";
return `
<div class="card-name">${meta.label}</div>
<div class="card-pips">${pips}</div>
<div class="card-meta card-rating-lbl">${lbl}</div>
<button class="card-log-btn">+ log</button>
`;
}
const modal = $("#log-modal");
const form = $("#log-form");
const typeEl = $("#log-type");
const valEl = $("#log-value");
const dateEl = $("#log-date");
const notesEl = $("#log-notes");
const ratingEl = $("#log-rating-value");
// Pip click handlers
let selectedRating = 0;
$$("#modal-pips .modal-pip").forEach(btn => {
btn.addEventListener("click", () => {
selectedRating = parseInt(btn.dataset.val);
ratingEl.value = selectedRating;
$$("#modal-pips .modal-pip").forEach(b => {
const v = parseInt(b.dataset.val);
b.classList.toggle("selected", v <= selectedRating);
});
});
});
function openModal(preType = null) {
form.reset();
selectedRating = 0;
ratingEl.value = "";
$$("#modal-pips .modal-pip").forEach(b => b.classList.remove("selected"));
dateEl.value = isoDate();
if (preType) typeEl.value = preType;
updateModalFields();
modal.showModal();
}
function openMeetingModal() {
form.reset();
dateEl.value = isoDate();
typeEl.value = "shaykh_meeting";
updateModalFields();
modal.showModal();
}
function updateModalFields() {
const type = typeEl.value;
const isRating = type === "wuqoof_qalbi";
const isMeeting = type === "shaykh_meeting";
$("#value-group").style.display = (!isRating && !isMeeting) ? "" : "none";
$("#rating-group").style.display = isRating ? "" : "none";
$("#shaykh-group").style.display = isMeeting ? "" : "none";
if (!isRating && !isMeeting) {
const meta = DAILY_WIRD[type];
if (meta) valEl.placeholder = `Target: ${meta.target} ${meta.unit}`;
}
}
typeEl.addEventListener("change", updateModalFields);
$("#modal-cancel").addEventListener("click", () => modal.close());
form.addEventListener("submit", async (e) => {
e.preventDefault();
const type = typeEl.value;
const isRating = type === "wuqoof_qalbi";
const isMtg = type === "shaykh_meeting";
if (isRating && !ratingEl.value) {
alert("Please select a rating.");
return;
}
const payload = {
wirdType: type,
date: dateEl.value,
value: isRating ? Number(ratingEl.value)
: isMtg ? 1
: Number(valEl.value),
notes: notesEl.value.trim() || null,
};
try {
const saved = await postEntry(payload);
allEntries.push(saved);
buildTodayMap();
buildMeetingLog();
renderToday();
renderMeetingPanel();
renderHistory();
renderChart();
modal.close();
} catch (err) {
alert("Could not save entry: " + err.message);
}
});
function renderMeetingPanel() {
const today = isoDate();
const last = meetingLog[0] || null;
const lastDate = last ? last.date : null;
const nextDate = lastDate ? addDays(lastDate, MEETING_CYCLE_DAYS) : null;
const daysToNext = nextDate ? daysBetween(today, nextDate) : null;
let statusHtml = "";
if (!lastDate) {
statusHtml = `<span class="meeting-status pending">No meetings logged yet</span>`;
} else if (daysToNext > 0) {
statusHtml = `<span class="meeting-status ok">Next due in <strong>${daysToNext}</strong> day${daysToNext !== 1 ? "s" : ""} &mdash; ${fmtDisplay(nextDate)}</span>`;
} else if (daysToNext === 0) {
statusHtml = `<span class="meeting-status due">Due today</span>`;
} else {
const ov = Math.abs(daysToNext);
statusHtml = `<span class="meeting-status overdue"><strong>${ov}</strong> day${ov !== 1 ? "s" : ""} overdue &mdash; expected ${fmtDisplay(nextDate)}</span>`;
}
const cycles = buildCycleInsights();
const cycleRows = cycles.map(c => `
<tr>
<td>${fmtDisplay(c.start)} &rarr; ${fmtDisplay(c.end)}</td>
<td>${c.met
? `<span class="badge done">✓ met — ${fmtDisplay(c.metOn)}</span>`
: c.ongoing
? `<span class="badge">ongoing</span>`
: `<span class="badge miss">✗ missed</span>`}
</td>
<td style="font-family:monospace;font-size:.7rem;color:var(--muted)">${c.met ? `day ${c.dayOfCycle}` : "—"}</td>
</tr>
`).join("");
$("#meeting-panel-body").innerHTML = `
<div id="meeting-status-row">
<div>
<span class="meeting-meta-lbl">Last meeting</span>
<span class="meeting-meta-val">${lastDate ? fmtDisplay(lastDate) : "—"}</span>
</div>
<div>
<span class="meeting-meta-lbl">Next expected</span>
<span class="meeting-meta-val">${nextDate ? fmtDisplay(nextDate) : "—"}</span>
</div>
<div id="meeting-status-msg">${statusHtml}</div>
</div>
<div id="meeting-cycles">
<h3>3-week cycles</h3>
${cycles.length === 0
? `<p style="color:var(--muted);font-size:.88rem">Log at least one meeting to see cycle insights.</p>`
: `<table class="meeting-cycle-table">
<thead><tr><th>Cycle window</th><th>Status</th><th>When</th></tr></thead>
<tbody>${cycleRows}</tbody>
</table>`}
</div>
`;
}
function buildCycleInsights() {
if (meetingLog.length === 0) return [];
const dates = [...meetingLog].reverse().map(e => e.date);
const today = isoDate();
const cycles = [];
let start = dates[0];
while (start <= today) {
const end = addDays(start, MEETING_CYCLE_DAYS - 1);
const hit = dates.find(d => d >= start && d <= end);
cycles.push({
start, end,
met: !!hit,
metOn: hit || null,
dayOfCycle: hit ? daysBetween(start, hit) + 1 : null,
ongoing: end > today,
});
start = addDays(end, 1);
}
return cycles.reverse();
}
function renderHistory() {
const tbody = $("#history-body");
tbody.innerHTML = "";
const sorted = [...allEntries]
.filter(e => e.wirdType !== "shaykh_meeting")
.sort((a, b) => b.date.localeCompare(a.date));
if (sorted.length === 0) {
tbody.innerHTML = `<tr><td colspan="5" class="loading-cell">No entries yet.</td></tr>`;
return;
}
for (const e of sorted) {
const meta = WIRD_META[e.wirdType] || {};
const cls = statusClass(e.wirdType, e.value);
const badgeTxt = cls === "over" ? (meta.type === "rating" ? "flourishing" : "above")
: cls === "done" ? (meta.type === "rating" ? "present" : "met")
: cls === "miss" ? (meta.type === "rating" ? "below" : "below")
: "—";
const tr = document.createElement("tr");
tr.innerHTML = `
<td style="font-family:monospace;font-size:.75rem">${e.date}</td>
<td>${meta.label || e.wirdType}</td>
<td style="font-family:monospace;font-size:.82rem">${fmtValue(e.wirdType, e.value)}</td>
<td><span class="badge ${cls}">${badgeTxt}</span></td>
<td style="color:var(--muted);font-size:.82rem">${e.notes || ""}</td>
`;
tbody.appendChild(tr);
}
}
function renderChart() {
const type = $("#trend-type").value;
const days = parseInt($("#trend-range").value);
const meta = WIRD_META[type];
const isRating = meta.type === "rating";
const now = new Date();
const labels = [];
const vals = [];
for (let i = days - 1; i >= 0; i--) {
const d = new Date(now);
d.setDate(d.getDate() - i);
const iso = isoDate(d);
labels.push(iso.slice(5));
const dayEntries = allEntries.filter(e => e.date === iso && e.wirdType === type);
if (isRating) {
// Use the last logged rating for the day
const last = dayEntries.sort((a,b) => a.createdAt > b.createdAt ? 1 : -1).pop();
vals.push(last ? Number(last.value) : 0);
} else {
vals.push(dayEntries.reduce((s, e) => s + Number(e.value), 0));
}
}
const nonZero = vals.filter(v => v > 0);
const avg = nonZero.length
? (isRating
? (nonZero.reduce((a,b)=>a+b,0)/nonZero.length).toFixed(1)
: Math.round(nonZero.reduce((a,b)=>a+b,0)/nonZero.length))
: 0;
const max = nonZero.length ? Math.max(...nonZero) : 0;
const streak = calcStreak(type);
if (isRating) {
$("#trend-stats").innerHTML = `
<div class="stat-chip"><span class="stat-val">${avg}</span><span class="stat-lbl">avg rating</span></div>
<div class="stat-chip"><span class="stat-val">${RATING_LABELS[max] || "—"}</span><span class="stat-lbl">best day</span></div>
<div class="stat-chip"><span class="stat-val">${streak}d</span><span class="stat-lbl">streak e ${meta.target}</span></div>
<div class="stat-chip"><span class="stat-val">${nonZero.length}</span><span class="stat-lbl">days logged</span></div>
`;
} else {
$("#trend-stats").innerHTML = `
<div class="stat-chip"><span class="stat-val">${Number(avg).toLocaleString()}</span><span class="stat-lbl">avg / day</span></div>
<div class="stat-chip"><span class="stat-val">${Number(max).toLocaleString()}</span><span class="stat-lbl">best day</span></div>
<div class="stat-chip"><span class="stat-val">${streak}d</span><span class="stat-lbl">current streak</span></div>
<div class="stat-chip"><span class="stat-val">${meta.target.toLocaleString()}</span><span class="stat-lbl">daily target</span></div>
`;
}
const cs = getComputedStyle(document.documentElement);
const accent = cs.getPropertyValue("--wird-accent").trim() || "#7c5c3a";
const gridColor = cs.getPropertyValue("--border").trim() || "#d7d7d7";
const tickColor = cs.getPropertyValue("--muted").trim() || "#666";
const ctx = $("#trend-chart").getContext("2d");
if (chart) chart.destroy();
chart = new Chart(ctx, {
type: "line",
data: {
labels,
datasets: [
{
label: meta.label,
data: vals,
borderColor: accent,
backgroundColor: accent + "22",
fill: true,
tension: .35,
pointRadius: 3,
pointHoverRadius: 5,
},
{
label: "Target",
data: vals.map(() => meta.target),
borderColor: "rgba(74,124,89,.5)",
borderDash: [5,4],
pointRadius: 0,
fill: false,
tension: 0,
},
],
},
options: {
responsive: true,
maintainAspectRatio: true,
plugins: {
legend: { display: false },
tooltip: {
callbacks: {
label: ctx => {
const v = ctx.parsed.y;
if (isRating && ctx.datasetIndex === 0)
return `${meta.label}: ${RATING_LABELS[v] || v} (${v}/5)`;
return `${ctx.dataset.label}: ${v}`;
},
},
},
},
scales: {
x: {
grid: { color: gridColor + "22" },
ticks: { color: tickColor, font: { family: "monospace", size: 10 }, maxTicksLimit: 10 },
},
y: {
grid: { color: gridColor + "22" },
ticks: {
color: tickColor,
font: { family: "monospace", size: 10 },
// For rating chart: show labels instead of numbers
...(isRating ? {
min: 0, max: 5, stepSize: 1,
callback: v => RATING_LABELS[v] || (v === 0 ? "" : v),
} : {}),
},
beginAtZero: true,
...(isRating ? { min: 0, max: 5 } : {}),
},
},
},
});
}
function calcStreak(type) {
const meta = WIRD_META[type];
let streak = 0;
const now = new Date();
for (let i = 0; i < 365; i++) {
const d = new Date(now);
d.setDate(d.getDate() - i);
const iso = isoDate(d);
const entries = allEntries.filter(e => e.date === iso && e.wirdType === type);
let total;
if (meta.type === "rating") {
const last = entries.sort((a,b) => a.createdAt > b.createdAt ? 1 : -1).pop();
total = last ? Number(last.value) : 0;
} else {
total = entries.reduce((s, e) => s + Number(e.value), 0);
}
if (total >= meta.target) streak++;
else if (i > 0) break;
}
return streak;
}
function bindControls() {
$("#open-log-btn").addEventListener("click", () => openModal());
$("#log-meeting-btn").addEventListener("click", openMeetingModal);
$("#trend-type").addEventListener("change", renderChart);
$("#trend-range").addEventListener("change", renderChart);
}
})();

View File

@@ -317,7 +317,7 @@ body.no-sidenotes {
.note {
position: relative;
padding: 1rem 1rem 0.9rem;
background: #fffef8;
background: --code-bg;
border-radius: 8px;
box-shadow:
0 2px 6px rgba(0, 0, 0, 0.08),
@@ -326,6 +326,7 @@ body.no-sidenotes {
transition:
transform 0.15s ease,
box-shadow 0.15s ease;
}
/* subtle randomness */
@@ -391,6 +392,7 @@ body.no-sidenotes {
font-size: 0.95rem;
line-height: 1.45;
white-space: pre-wrap;
color: --fg;
}
/* =========================
@@ -424,7 +426,7 @@ body.no-sidenotes {
#notes-form {
width: 100%;
max-width: 420px;
background: #fffef8;
background: --bg;
padding: 1.25rem 1.25rem 1.1rem;
border-radius: 10px;
box-shadow:

Binary file not shown.

View File

@@ -1,28 +0,0 @@
#+TITLE: 2025 List
#+OPTIONS: toc:nil num:nil
See the categories: @@html:<a href="../../home/categories.html">Categories</a>@@
* 2025
** December 2025
- [[file:12-december/28-12-week-review.org][[28-12-2025] - Weekly Review]] @@html:<span class="post-date">28-12-2025 12:12</span>@@ @@html:<a href="/tags/review.html"> <span class="post-tag">review</span> </a>@@
- [[file:12-december/21-12-week-review.org][[21-12-2025] - Weekly Review]] @@html:<span class="post-date">21-12-2025 12:12</span>@@ @@html:<a href="/tags/review.html"> <span class="post-tag">review</span> </a>@@
- [[file:12-december/14-12-week-review.org][[14-12-2025] - Weekly Review]] @@html:<span class="post-date">09-12-2025 12:12</span>@@ @@html:<a href="/tags/review.html"> <span class="post-tag">review</span> </a>@@
- [[file:12-december/07-12-week-review.org][[07-12-2025] - Weekly Review]] @@html:<span class="post-date">07-12-2025 20:34</span>@@ @@html:<a href="/tags/review.html"> <span class="post-tag">review</span> </a>@@
** November 2025
- [[file:11-november/30-11-week-review.org][[30-11-2025] - Weekly Review]] @@html:<span class="post-date">30-11-2025 17:09</span>@@ @@html:<a href="/tags/review.html"> <span class="post-tag">review</span> </a>@@
- [[file:11-november/23-11-week-review.org][[23-11-2025] - Weekly Review]] @@html:<span class="post-date">17-11-2025 18:05</span>@@ @@html:<a href="/tags/review.html"> <span class="post-tag">review</span> </a>@@
- [[file:11-november/16-11-week-review.org][[16-11-2025] - Weekly Review]] @@html:<span class="post-date">10-11-2025 17:44</span>@@ @@html:<a href="/tags/review.html"> <span class="post-tag">review</span> </a>@@
- [[file:11-november/09-11-week-review.org][[09-11-2025] - Weekly Review]] @@html:<span class="post-date">09-11-2025 20:08</span>@@ @@html:<a href="/tags/review.html"> <span class="post-tag">review</span> </a>@@
- [[file:11-november/02-11-week-review.org][[02-11-2025] - Weekly Review]] @@html:<span class="post-date">02-11-2025 00:00</span>@@ @@html:<a href="/tags/review.html"> <span class="post-tag">review</span> </a>@@
** August 2025
- [[file:08-august/third-time.org][Third Time]] @@html:<span class="post-date">28-08-2025 17:08</span>@@ @@html:<a href="/tags/insights.html"> <span class="post-tag">insights</span> </a>@@
- [[file:08-august/benefits-of-reading.org][Benefits of Reading]] @@html:<span class="post-date">14-08-2025 23:36</span>@@ @@html:<a href="/tags/reading.html"> <span class="post-tag">reading</span> </a>@@ @@html:<a href="/tags/insights.html"> <span class="post-tag">insights</span> </a>@@
- [[file:08-august/hilberts.hotel.org][Hilbert's Hotel]] @@html:<span class="post-date">11-08-2025 18:39</span>@@ @@html:<a href="/tags/maths.html"> <span class="post-tag">maths</span> </a>@@ @@html:<a href="/tags/insights.html"> <span class="post-tag">insights</span> </a>@@
- [[file:08-august/spending-the-whole-day-on-this-website.org][09-08-2025: Website Changes]] @@html:<span class="post-date">09-08-2025 00:00</span>@@ @@html:<a href="/tags/emacs.html"> <span class="post-tag">emacs</span> </a>@@ @@html:<a href="/tags/website.html"> <span class="post-tag">website</span> </a>@@
- [[file:08-august/what-do-i-want-to-do-with-emacs.org][What Do I Want To Do With Emacs]] @@html:<span class="post-date">08-08-2025 00:00</span>@@ @@html:<a href="/tags/emacs.html"> <span class="post-tag">emacs</span> </a>@@ @@html:<a href="/tags/review.html"> <span class="post-tag">review</span> </a>@@
- [[file:08-august/wacom-with-arch.org][Wacom With Arch]] @@html:<span class="post-date">08-08-2025 00:00</span>@@ @@html:<a href="/tags/insights.html"> <span class="post-tag">insights</span> </a>@@
- [[file:08-august/zettelkasten.org][Zettelkasten Method]] @@html:<span class="post-date">07-08-2025 00:00</span>@@ @@html:<a href="/tags/education.html"> <span class="post-tag">education</span> </a>@@

View File

@@ -0,0 +1,15 @@
#+TITLE: [08-03-2026] - Weekly Review
#+OPTIONS: num:nil
#+DATE: <2026-03-08 Sun 12:00>
#+filetags: :review:
#+COMMENTS: t
#+SLUG: 08-03-26-week-review
* Review
Another work week done. This week was spent mostly working on the Journeys upgrade (again), but we are nearing completion. Towards the end of the week we had to drop what we were doing to focus on getting Site Visits to live. I hope that In'sha'Allah I can do the deployment process for Journeys as there is a lot of learning potential.
Unfortunately Hyper-V broke on my laptop, so I have to go in the office next monday to get it repaired.
* Timesheet
[[../../../assets/images/reviews/timesheets/timesheet-08-03-26.png]]

View File

@@ -0,0 +1,20 @@
#+TITLE: [15-03-2026] - Weekly Review
#+OPTIONS: num:nil
#+DATE: <2026-03-15 Sun 12:00>
#+filetags: :review:
#+COMMENTS: t
#+SLUG: 15-03-26-week-review
* Review
This week was a four day work week. I had no plans on booking the Friday off but a message came saying we were invited to perform Nafl I'tikaaf. Thus, it hit me that this is the last Friday of Ramadan so I should make the most of it. In'sha'Allah I plan to stay until Monday after Fajr (writing this as of <2026-03-12 Thu>).
I finally finished the upgrades for Journeys Web API. I released a PR on <2026-03-12 Thu> and should get to resolving the comments on Monday In'sha'Allah.
Over 100 commits on this single PR...
[[../../../assets/images/blogs/2026-03-12-journeys-branch.png]]
* Timesheet
[[../../../assets/images/reviews/timesheets/2026-03-15-timesheet.png]]

View File

@@ -0,0 +1,8 @@
#+TITLE: Feeling extremely sleepy
#+OPTIONS: num:nil
#+DATE: <2026-03-16 Mon 16:18>
#+filetags: :life:
#+COMMENTS: t
#+SLUG: feeling-sleepy
One thing I realised about myself is that if I feel /sleepy/ (as in running on fumes), then I will literally go unconscious, as I can't tell the difference between reality and dream.

View File

@@ -0,0 +1,10 @@
#+TITLE: DAG fixes
#+OPTIONS: num:nil
#+DATE: <2026-03-18 Wed 15:01>
#+filetags: :life:
#+COMMENTS: t
#+SLUG: fixing-the-dag-18-03
What an interesting morning. So yesterday, my manager sent me a DM asking to look into the DAG stuff (was failing for a few days due to the restructuring of SQ).
[[../../../assets/images/blogs/2026-03-18-slack-dag-manager.png]]

View File

@@ -0,0 +1,10 @@
#+TITLE: Oversleeping and missing a meeting...
#+OPTIONS: num:nil
#+DATE: <2026-03-16 Mon 11:21>
#+filetags: :life:
#+COMMENTS: t
#+SLUG: oversleeping-16-03
Just this morning, I went to sleep at 6 am, due to spending the night elsewhere, and had some urgent things to do. I ended up waking up at 9:30, which meant the daily standup just finished. Never again.
Sleeping in and missing a meeting is a negative million kind of feeling.

View File

@@ -0,0 +1,9 @@
#+TITLE: Setting a Wird Tracker
#+OPTIONS: num:nil
#+DATE: <2026-03-19 Thu 13:15>
#+filetags: :life:
#+COMMENTS: t
#+SLUG: setting-a-wird-tracker-19-03
I decided it was time to use the extensibility of this website to add an [[../../../home/wird-tracker.org][Awrad tracker]], which would serve as a place for me to track the litanies on a daily basis. I used claude to generate the architecture and to give the frontend and backend for it. It did a good job {{{sidenote(1, It also generated a comprehensive documentation here https://zainezq.com/home/guide/wird-tracker-guide.html)}}}.

View File

@@ -1,25 +0,0 @@
#+TITLE: 2026 List
#+OPTIONS: toc:nil num:nil
See the categories: @@html:<a href="../../home/categories.html">Categories</a>@@
* 2026
** March 2026
- [[file:03-march/01-03-week-review.org][[01-03-2026] - Weekly Review]] @@html:<span class="post-date">01-03-2026 12:00</span>@@ @@html:<a href="/tags/review.html"> <span class="post-tag">review</span> </a>@@
** February 2026
- [[file:02-february/27-02-26.org][Journeys rambles again...]] @@html:<span class="post-date">27-02-2026 17:12</span>@@ @@html:<a href="/tags/life.html"> <span class="post-tag">life</span> </a>@@
- [[file:02-february/26-02-26.org][Starting the Journeys Upgrade]] @@html:<span class="post-date">26-02-2026 17:32</span>@@ @@html:<a href="/tags/life.html"> <span class="post-tag">life</span> </a>@@
- [[file:02-february/24-02-26.org][Integration tests failing (sob)]] @@html:<span class="post-date">24-02-2026 16:55</span>@@ @@html:<a href="/tags/life.html"> <span class="post-tag">life</span> </a>@@
- [[file:02-february/22-02-week-review.org][[22-02-2026] - Weekly Review]] @@html:<span class="post-date">22-02-2026 12:00</span>@@ @@html:<a href="/tags/review.html"> <span class="post-tag">review</span> </a>@@
- [[file:02-february/15-02-week-review.org][[15-02-2026] - Weekly Review]] @@html:<span class="post-date">15-02-2026 12:00</span>@@ @@html:<a href="/tags/review.html"> <span class="post-tag">review</span> </a>@@
- [[file:02-february/08-02-week-review.org][[08-02-2026] - Weekly Review]] @@html:<span class="post-date">08-02-2026 12:00</span>@@ @@html:<a href="/tags/review.html"> <span class="post-tag">review</span> </a>@@
- [[file:02-february/01-02-week-review.org][[01-02-2026] - Weekly Review]] @@html:<span class="post-date">01-02-2026 12:00</span>@@ @@html:<a href="/tags/review.html"> <span class="post-tag">review</span> </a>@@
- [[file:02-february/third-meeting.org][Third Meeting with lima :)]] @@html:<span class="post-date">01-02-2026 12:00</span>@@ @@html:<a href="/tags/life.html"> <span class="post-tag">life</span> </a>@@
** January 2026
- [[file:01-january/25-01-week-review.org][[25-01-2026] - Weekly Review]] @@html:<span class="post-date">25-01-2026 12:00</span>@@ @@html:<a href="/tags/review.html"> <span class="post-tag">review</span> </a>@@
- [[file:01-january/18-01-week-review.org][[18-01-2026] - Weekly Review]] @@html:<span class="post-date">18-01-2026 12:12</span>@@ @@html:<a href="/tags/review.html"> <span class="post-tag">review</span> </a>@@
- [[file:01-january/11-01-week-review.org][[11-01-2026] - Weekly Review]] @@html:<span class="post-date">11-01-2026 12:12</span>@@ @@html:<a href="/tags/review.html"> <span class="post-tag">review</span> </a>@@
- [[file:01-january/04-01-week-review.org][[04-01-2026] - Weekly Review]] @@html:<span class="post-date">04-01-2026 12:12</span>@@ @@html:<a href="/tags/review.html"> <span class="post-tag">review</span> </a>@@

View File

@@ -3,37 +3,54 @@
See the categories: @@html:<a href="../home/categories.html">Categories</a>@@
* Blogs:
- [[file:2026/2026-list.org][2026 List]] @@html:<span class="post-date">08-03-2026 17:33</span>@@
- [[file:2025/2025-list.org][2025 List]] @@html:<span class="post-date">08-03-2026 17:33</span>@@
* 2026
** March 2026
- [[file:2026/03-march/setting-a-wird-tracker-19-03.org][Setting a Wird Tracker]] @@html:<span class="post-date">19-03-2026 13:15</span>@@ @@html:<a href="/tags/life.html"> <span class="post-tag">life</span> </a>@@
- [[file:2026/03-march/fixing-the-dag-18-03.org][DAG fixes]] @@html:<span class="post-date">18-03-2026 15:01</span>@@ @@html:<a href="/tags/life.html"> <span class="post-tag">life</span> </a>@@
- [[file:2026/03-march/feeling-sleepy.org][Feeling extremely sleepy]] @@html:<span class="post-date">16-03-2026 16:18</span>@@ @@html:<a href="/tags/life.html"> <span class="post-tag">life</span> </a>@@
- [[file:2026/03-march/oversleeping-16-03.org][Oversleeping and missing a meeting...]] @@html:<span class="post-date">16-03-2026 11:21</span>@@ @@html:<a href="/tags/life.html"> <span class="post-tag">life</span> </a>@@
- [[file:2026/03-march/15-03-week-review.org][[15-03-2026] - Weekly Review]] @@html:<span class="post-date">15-03-2026 12:00</span>@@ @@html:<a href="/tags/review.html"> <span class="post-tag">review</span> </a>@@
- [[file:2026/03-march/08-03-week-review.org][[08-03-2026] - Weekly Review]] @@html:<span class="post-date">08-03-2026 12:00</span>@@ @@html:<a href="/tags/review.html"> <span class="post-tag">review</span> </a>@@
- [[file:2026/03-march/01-03-week-review.org][[01-03-2026] - Weekly Review]] @@html:<span class="post-date">01-03-2026 12:00</span>@@ @@html:<a href="/tags/review.html"> <span class="post-tag">review</span> </a>@@
** February 2026
- [[file:2026/02-february/27-02-26.org][Journeys rambles again...]] @@html:<span class="post-date">27-02-2026 17:12</span>@@ @@html:<a href="/tags/life.html"> <span class="post-tag">life</span> </a>@@
- [[file:2026/02-february/26-02-26.org][Starting the Journeys Upgrade]] @@html:<span class="post-date">26-02-2026 17:32</span>@@ @@html:<a href="/tags/life.html"> <span class="post-tag">life</span> </a>@@
- [[file:2026/02-february/24-02-26.org][Integration tests failing (sob)]] @@html:<span class="post-date">24-02-2026 16:55</span>@@ @@html:<a href="/tags/life.html"> <span class="post-tag">life</span> </a>@@
- [[file:2026/02-february/22-02-week-review.org][[22-02-2026] - Weekly Review]] @@html:<span class="post-date">22-02-2026 12:00</span>@@ @@html:<a href="/tags/review.html"> <span class="post-tag">review</span> </a>@@
- [[file:2026/02-february/15-02-week-review.org][[15-02-2026] - Weekly Review]] @@html:<span class="post-date">15-02-2026 12:00</span>@@ @@html:<a href="/tags/review.html"> <span class="post-tag">review</span> </a>@@
- [[file:2026/02-february/08-02-week-review.org][[08-02-2026] - Weekly Review]] @@html:<span class="post-date">08-02-2026 12:00</span>@@ @@html:<a href="/tags/review.html"> <span class="post-tag">review</span> </a>@@
- [[file:2026/02-february/01-02-week-review.org][[01-02-2026] - Weekly Review]] @@html:<span class="post-date">01-02-2026 12:00</span>@@ @@html:<a href="/tags/review.html"> <span class="post-tag">review</span> </a>@@
- [[file:2026/02-february/third-meeting.org][Third Meeting with lima :)]] @@html:<span class="post-date">01-02-2026 12:00</span>@@ @@html:<a href="/tags/life.html"> <span class="post-tag">life</span> </a>@@
- [[file:2026/02-february/01-02-week-review.org][[01-02-2026] - Weekly Review]] @@html:<span class="post-date">01-02-2026 12:00</span>@@ @@html:<a href="/tags/review.html"> <span class="post-tag">review</span> </a>@@
** January 2026
- [[file:2026/01-january/25-01-week-review.org][[25-01-2026] - Weekly Review]] @@html:<span class="post-date">25-01-2026 12:00</span>@@ @@html:<a href="/tags/review.html"> <span class="post-tag">review</span> </a>@@
- [[file:2026/01-january/18-01-week-review.org][[18-01-2026] - Weekly Review]] @@html:<span class="post-date">18-01-2026 12:12</span>@@ @@html:<a href="/tags/review.html"> <span class="post-tag">review</span> </a>@@
- [[file:2026/01-january/11-01-week-review.org][[11-01-2026] - Weekly Review]] @@html:<span class="post-date">11-01-2026 12:12</span>@@ @@html:<a href="/tags/review.html"> <span class="post-tag">review</span> </a>@@
- [[file:2026/01-january/04-01-week-review.org][[04-01-2026] - Weekly Review]] @@html:<span class="post-date">04-01-2026 12:12</span>@@ @@html:<a href="/tags/review.html"> <span class="post-tag">review</span> </a>@@
* 2025
** December 2025
- [[file:2025/12-december/28-12-week-review.org][[28-12-2025] - Weekly Review]] @@html:<span class="post-date">28-12-2025 12:12</span>@@ @@html:<a href="/tags/review.html"> <span class="post-tag">review</span> </a>@@
- [[file:2025/12-december/21-12-week-review.org][[21-12-2025] - Weekly Review]] @@html:<span class="post-date">21-12-2025 12:12</span>@@ @@html:<a href="/tags/review.html"> <span class="post-tag">review</span> </a>@@
- [[file:2025/12-december/14-12-week-review.org][[14-12-2025] - Weekly Review]] @@html:<span class="post-date">09-12-2025 12:12</span>@@ @@html:<a href="/tags/review.html"> <span class="post-tag">review</span> </a>@@
- [[file:2025/12-december/07-12-week-review.org][[07-12-2025] - Weekly Review]] @@html:<span class="post-date">07-12-2025 20:34</span>@@ @@html:<a href="/tags/review.html"> <span class="post-tag">review</span> </a>@@
** November 2025
- [[file:2025/11-november/30-11-week-review.org][[30-11-2025] - Weekly Review]] @@html:<span class="post-date">30-11-2025 17:09</span>@@ @@html:<a href="/tags/review.html"> <span class="post-tag">review</span> </a>@@
- [[file:2025/11-november/23-11-week-review.org][[23-11-2025] - Weekly Review]] @@html:<span class="post-date">17-11-2025 18:05</span>@@ @@html:<a href="/tags/review.html"> <span class="post-tag">review</span> </a>@@
- [[file:2025/11-november/16-11-week-review.org][[16-11-2025] - Weekly Review]] @@html:<span class="post-date">10-11-2025 17:44</span>@@ @@html:<a href="/tags/review.html"> <span class="post-tag">review</span> </a>@@
- [[file:2025/11-november/09-11-week-review.org][[09-11-2025] - Weekly Review]] @@html:<span class="post-date">09-11-2025 20:08</span>@@ @@html:<a href="/tags/review.html"> <span class="post-tag">review</span> </a>@@
- [[file:publish-pages.org][How to publish pages using Org Publish]] @@html:<span class="post-date">08-11-2025 10:57</span>@@ @@html:<a href="/tags/website.html"> <span class="post-tag">website</span> </a>@@
- [[file:2025/11-november/02-11-week-review.org][[02-11-2025] - Weekly Review]] @@html:<span class="post-date">02-11-2025 00:00</span>@@ @@html:<a href="/tags/review.html"> <span class="post-tag">review</span> </a>@@
** August 2025
- [[file:2025/08-august/third-time.org][Third Time]] @@html:<span class="post-date">28-08-2025 17:08</span>@@ @@html:<a href="/tags/insights.html"> <span class="post-tag">insights</span> </a>@@
- [[file:2025/08-august/benefits-of-reading.org][Benefits of Reading]] @@html:<span class="post-date">14-08-2025 23:36</span>@@ @@html:<a href="/tags/reading.html"> <span class="post-tag">reading</span> </a>@@ @@html:<a href="/tags/insights.html"> <span class="post-tag">insights</span> </a>@@
- [[file:2025/08-august/hilberts.hotel.org][Hilbert's Hotel]] @@html:<span class="post-date">11-08-2025 18:39</span>@@ @@html:<a href="/tags/maths.html"> <span class="post-tag">maths</span> </a>@@ @@html:<a href="/tags/insights.html"> <span class="post-tag">insights</span> </a>@@
- [[file:2025/08-august/spending-the-whole-day-on-this-website.org][09-08-2025: Website Changes]] @@html:<span class="post-date">09-08-2025 00:00</span>@@ @@html:<a href="/tags/emacs.html"> <span class="post-tag">emacs</span> </a>@@ @@html:<a href="/tags/website.html"> <span class="post-tag">website</span> </a>@@
- [[file:2025/08-august/what-do-i-want-to-do-with-emacs.org][What Do I Want To Do With Emacs]] @@html:<span class="post-date">08-08-2025 00:00</span>@@ @@html:<a href="/tags/emacs.html"> <span class="post-tag">emacs</span> </a>@@ @@html:<a href="/tags/review.html"> <span class="post-tag">review</span> </a>@@
- [[file:2025/08-august/wacom-with-arch.org][Wacom With Arch]] @@html:<span class="post-date">08-08-2025 00:00</span>@@ @@html:<a href="/tags/insights.html"> <span class="post-tag">insights</span> </a>@@
- [[file:2025/08-august/what-do-i-want-to-do-with-emacs.org][What Do I Want To Do With Emacs]] @@html:<span class="post-date">08-08-2025 00:00</span>@@ @@html:<a href="/tags/emacs.html"> <span class="post-tag">emacs</span> </a>@@ @@html:<a href="/tags/review.html"> <span class="post-tag">review</span> </a>@@
- [[file:2025/08-august/zettelkasten.org][Zettelkasten Method]] @@html:<span class="post-date">07-08-2025 00:00</span>@@ @@html:<a href="/tags/education.html"> <span class="post-tag">education</span> </a>@@
- [[file:blogs-intro.org][Blogs Introduction]] @@html:<span class="post-date">06-08-2025 00:00</span>@@ @@html:<a href="/tags/introduction.html"> <span class="post-tag">introduction</span> </a>@@

View File

@@ -60,6 +60,7 @@
<link rel=\"stylesheet\" href=\"/assets/styles/misc.css\" />
<link rel=\"stylesheet\" href=\"/assets/styles/toc.css\" />
<link rel=\"stylesheet\" href=\"/assets/styles/media.css\" />
<link rel=\"stylesheet\" href=\"/assets/styles/wird-tracker.css\" />
<script src=\"/assets/scripts/script.js\" defer></script>
<script src=\"/assets/scripts/lunr.js\" defer></script>
@@ -71,6 +72,7 @@
<script src=\"/assets/scripts/svg-pan-zoom.min.js\" defer></script>
<script src=\"/assets/scripts/gallery-init.js\" defer></script>
<script src=\"/assets/scripts/sitemap-interactive.js\" defer></script>
<script src=\"/assets/scripts/wird-tracker.js\" defer></script>
"
)
@@ -96,8 +98,7 @@
<a href=\"/\"> <img src=\"/assets/images/gr.png\" alt=\"Site Logo\" class=\"banner-logo\" /> </a>
<nav>
<a href=\"/\">Home | </a>
<a href=\"/blogs/2026/2026-list.html \">2026 | </a>
<a href=\"/blogs/blogs-list.html\">Blogs | </a>
<a href=\"/blogs/blogs-list.html \">Blogs | </a>
<a href=\"/posts/career/career-list.html\">Career | </a>
<a href=\"https://zone.zainezq.com\">Dashboard</a>
@@ -401,7 +402,7 @@ A file has comments if:
:sitemap-filename "blogs-list.org"
:sitemap-title "Blogs List"
:sitemap-style list
:sitemap-function z/blogs-sitemap
:sitemap-function z/blogs-grouped-sitemap
:sitemap-sort-files anti-chronologically
:html-head ,z-shared-head)
@@ -421,37 +422,39 @@ A file has comments if:
:sitemap-sort-files anti-chronologically
:html-head ,z-shared-head)
("org-2025"
:base-directory ,(site-path "blogs/2025/")
:publishing-directory ,(site-path "output/blogs/2025/")
:recursive t
:base-extension "org"
:publishing-function z/z-publish-to-html
:html-preamble ,z-preamble
:html-postamble ,z-postamble
:auto-sitemap t
:sitemap-filename "2025-list.org"
:sitemap-title "2025 List"
:sitemap-style list
:sitemap-function z/2025-sitemap
:sitemap-sort-files anti-chronologically
:html-head ,z-shared-head)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ("org-2025" ;;
;; :base-directory ,(site-path "blogs/2025/") ;;
;; :publishing-directory ,(site-path "output/blogs/2025/") ;;
;; :recursive t ;;
;; :base-extension "org" ;;
;; :publishing-function z/z-publish-to-html ;;
;; :html-preamble ,z-preamble ;;
;; :html-postamble ,z-postamble ;;
;; :auto-sitemap t ;;
;; :sitemap-filename "2025-list.org" ;;
;; :sitemap-title "2025 List" ;;
;; :sitemap-style list ;;
;; :sitemap-function z/2025-sitemap ;;
;; :sitemap-sort-files anti-chronologically ;;
;; :html-head ,z-shared-head) ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
("org-2026"
:base-directory ,(site-path "blogs/2026/")
:publishing-directory ,(site-path "output/blogs/2026/")
:recursive t
:base-extension "org"
:publishing-function z/z-publish-to-html
:html-preamble ,z-preamble
:html-postamble ,z-postamble
:auto-sitemap t
:sitemap-filename "2026-list.org"
:sitemap-title "2026 List"
:sitemap-style list
:sitemap-function z/2026-sitemap
:sitemap-sort-files anti-chronologically
:html-head ,z-shared-head)
;; ("org-2026"
;; :base-directory ,(site-path "blogs/2026/")
;; :publishing-directory ,(site-path "output/blogs/2026/")
;; :recursive t
;; :base-extension "org"
;; :publishing-function z/z-publish-to-html
;; :html-preamble ,z-preamble
;; :html-postamble ,z-postamble
;; :auto-sitemap t
;; :sitemap-filename "2026-list.org"
;; :sitemap-title "2026 List"
;; :sitemap-style list
;; :sitemap-function z/2026-sitemap
;; :sitemap-sort-files anti-chronologically
;; :html-head ,z-shared-head)
("org-career"
:base-directory ,(site-path "posts/career/")

16636
build.log

File diff suppressed because one or more lines are too long

View File

@@ -6,10 +6,10 @@
- [[file:../tags/emacs.org][@@html:<span class="post-tag">emacs</span>@@]] (2)
- [[file:../tags/insights.org][@@html:<span class="post-tag">insights</span>@@]] (4)
- [[file:../tags/introduction.org][@@html:<span class="post-tag">introduction</span>@@]] (3)
- [[file:../tags/learning.org][@@html:<span class="post-tag">learning</span>@@]] (14)
- [[file:../tags/life.org][@@html:<span class="post-tag">life</span>@@]] (4)
- [[file:../tags/learning.org][@@html:<span class="post-tag">learning</span>@@]] (16)
- [[file:../tags/life.org][@@html:<span class="post-tag">life</span>@@]] (8)
- [[file:../tags/maths.org][@@html:<span class="post-tag">maths</span>@@]] (1)
- [[file:../tags/notes.org][@@html:<span class="post-tag">notes</span>@@]] (15)
- [[file:../tags/notes.org][@@html:<span class="post-tag">notes</span>@@]] (17)
- [[file:../tags/reading.org][@@html:<span class="post-tag">reading</span>@@]] (1)
- [[file:../tags/review.org][@@html:<span class="post-tag">review</span>@@]] (20)
- [[file:../tags/review.org][@@html:<span class="post-tag">review</span>@@]] (22)
- [[file:../tags/website.org][@@html:<span class="post-tag">website</span>@@]] (2)

View File

@@ -0,0 +1,332 @@
#+TITLE: Wird Tracker — Technical Guide
#+OPTIONS: toc:t num:t
#+SLUG: wird-tracker-guide
* Overview
The wird tracker is a full-stack feature built on top of the existing
org-publish site. It consists of four layers:
- *Database* — a PostgreSQL table (~wird_entries~) storing every log event
- *Backend* — a Spring Boot controller (~WirdController~) exposing a REST API at ~/api/wird/~
- *Frontend JS*~wird-tracker.js~ handles all rendering and API calls
- *Org page*~wird-tracker.org~ defines the HTML structure via ~#+BEGIN_EXPORT html~
The page has no sidenotes (~#+NO_SIDENOTES: t~), so ~body.no-sidenotes~ in
~styles.css~ automatically widens the content area. ~wird-tracker.css~
complements ~styles.css~ and defers to its CSS variables, so dark mode
works without any extra work.
* File Locations
| File | Where it lives | Purpose |
|--------------------------+---------------------------------------------+----------------------------------|
| ~wird-tracker.org~ | your org source directory | page structure, HTML injection |
| ~wird-tracker.css~ | ~static/css/wird-tracker.css~ | component styles |
| ~wird-tracker.js~ | ~static/js/wird-tracker.js~ | all frontend logic |
| ~WirdEntry.java~ | ~src/.../model/WirdEntry.java~ | JPA entity |
| ~CreateWirdEntryDTO.java~ | ~src/.../dto/CreateWirdEntryDTO.java~ | request body shape |
| ~WirdEntryRepository.java~ | ~src/.../repository/WirdEntryRepository.java~ | Spring Data queries |
| ~WirdService.java~ | ~src/.../service/WirdService.java~ | business logic |
| ~WirdController.java~ | ~src/.../controller/WirdController.java~ | REST endpoints |
| ~wird_schema.sql~ | wherever you keep your SQL scripts | initial DB setup |
* Database Schema
** Tables
The main table is ~wird_entries~. Each row is a single log event — not
one row per day. This means you can log 200 durood in the morning and 300
in the evening; they aggregate to 500 on the frontend.
#+BEGIN_SRC sql
CREATE TABLE wird_entries (
id BIGSERIAL PRIMARY KEY,
wird_type VARCHAR NOT NULL,
date DATE NOT NULL DEFAULT CURRENT_DATE,
value NUMERIC(10,2) NOT NULL CHECK (value >= 0),
notes TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
#+END_SRC
The ~wird_targets~ table stores daily minimums per wird type. It has an
~effective_from~ column so you can change targets over time without
losing history.
#+BEGIN_SRC sql
CREATE TABLE wird_targets (
id SERIAL PRIMARY KEY,
wird_type VARCHAR NOT NULL,
target NUMERIC NOT NULL,
effective_from DATE NOT NULL DEFAULT CURRENT_DATE,
UNIQUE (wird_type, effective_from)
);
#+END_SRC
There is also a convenience view ~wird_daily_totals~ which aggregates
entries per day per type. It is not queried by the backend currently but
is useful for ad-hoc psql inspection.
** Note on the Enum
The schema was originally written with a PostgreSQL ~CREATE TYPE wird_type AS ENUM~.
This was dropped in favour of plain ~VARCHAR~ because Hibernate 6 cannot
bind a ~String~ to a Postgres enum column without a custom ~PGobject~
converter, which itself has compatibility issues with Hibernate 6's type
system. ~VARCHAR~ with application-level validation is simpler and
equally safe.
* REST API
All endpoints are under ~/api/wird/~.
| Method | Path | Description |
|--------+-----------------------------+---------------------------------------------|
| ~GET~ | ~/api/wird/entries~ | All entries, newest first |
| ~GET~ | ~/api/wird/entries/today~ | Today's entries only |
| ~GET~ | ~/api/wird/entries/range~ | Entries between ~?from=YYYY-MM-DD&to=...~ |
| ~GET~ | ~/api/wird/entries/type/:t~ | Entries for one type in a date range |
| ~POST~ | ~/api/wird/entries~ | Create a new entry |
The ~POST~ body shape is:
#+BEGIN_SRC json
{
"wirdType": "durood",
"date": "2026-03-19",
"value": 500,
"notes": "after fajr"
}
#+END_SRC
All fields except ~notes~ are required. ~date~ defaults to today on the
backend if omitted, but the frontend always sends it explicitly.
* Frontend Architecture
** Key constants
At the top of ~wird-tracker.js~ there are two objects you will edit most often:
#+BEGIN_SRC javascript
const DAILY_WIRD = {
durood: { label: "Durood", unit: "count", target: 500 },
istighfar: { label: "Istighfar", unit: "count", target: 200 },
quran: { label: "Qurʾān", unit: "juz", target: 3 },
muraqabah: { label: "Murāqabah", unit: "min", target: 20 },
wuqoof_qalbi: { label: "Wuqūf Qalbī", unit: "min", target: 15 },
};
const MEETING_CYCLE_DAYS = 21;
#+END_SRC
~DAILY_WIRD~ drives the today cards, the progress bar count, the trend
chart dropdown, and the history table. ~WERD_META~ is a superset of
~DAILY_WIRD~ that also includes ~shaykh_meeting~ — used for labelling
history entries and formatting values.
** Rendering pipeline
On page load, the sequence is:
1. ~loadAll()~ — fetches ~/api/wird/entries~, populates ~allEntries~
2. ~buildTodayMap()~ — aggregates today's entries into ~todayMap~ (type → total)
3. ~buildMeetingLog()~ — filters ~allEntries~ to attended shaykh meetings
4. ~renderToday()~ — draws the 5 wird cards and progress bar
5. ~renderMeetingPanel()~ — draws the meeting status, next due date, cycle table
6. ~renderHistory()~ — populates the history table (meetings excluded)
7. ~renderChart()~ — draws the Chart.js trend line for the selected wird
After any ~POST~ (new entry), steps 27 all re-run so the page updates
without a reload.
** Shaykh meeting cycle logic
~buildCycleInsights()~ works by anchoring cycles to the date of your very
first logged meeting and walking forward in 21-day windows until today.
For each window it checks whether any attended meeting falls within it.
This means the cycle boundaries are stable — they do not shift when you
log a new meeting. If you want cycles to reset from the most recent
meeting instead, change the anchor line:
#+BEGIN_SRC javascript
// Current: anchored to first ever meeting
const first = allMeetingDates[0];
// Alternative: rolling window from most recent
const first = addDays(allMeetingDates[allMeetingDates.length - 1], 0);
#+END_SRC
* How To: Common Tasks
** Change a daily target
Targets are currently hard-coded in ~DAILY_WIRD~ in ~wird-tracker.js~.
Change the ~target~ value for the relevant entry:
#+BEGIN_SRC javascript
durood: { label: "Durood", unit: "count", target: 700 },
#+END_SRC
If you want targets to come from the database instead (so you can change
them without redeploying), the ~wird_targets~ table already supports this.
You would need to add a ~/api/wird/targets~ endpoint in ~WirdController~
and fetch it in ~loadAll()~, then replace the hard-coded ~target~ values
with the fetched ones.
** Add a new wird type
There are four places to update:
1. *~wird-tracker.js~* — add an entry to ~DAILY_WIRD~:
#+BEGIN_SRC javascript
tawbah: { label: "Tawbah", unit: "count", target: 100 },
#+END_SRC
2. *~wird-tracker.org~* — add an ~<option>~ to the modal ~<select>~:
#+BEGIN_SRC html
<option value="tawbah">Tawbah</option>
#+END_SRC
3. *~wird-tracker.org~* — add an ~<option>~ to the trend chart ~<select>~:
#+BEGIN_SRC html
<option value="tawbah">Tawbah</option>
#+END_SRC
4. *~wird_targets~ table* — insert a default target (optional but tidy):
#+BEGIN_SRC sql
INSERT INTO wird_targets (wird_type, target)
VALUES ('tawbah', 100);
#+END_SRC
No backend changes are needed — ~WirdController~ accepts any string as
~wirdType~ and stores it as-is.
** Change the shaykh meeting cycle length
One line in ~wird-tracker.js~:
#+BEGIN_SRC javascript
const MEETING_CYCLE_DAYS = 21; // change to e.g. 14
#+END_SRC
** Edit or delete an entry
There is currently no edit/delete UI. You can do it directly in psql:
#+BEGIN_SRC sql
-- Find the entry
SELECT * FROM wird_entries
WHERE wird_type = 'durood' AND date = '2026-03-19'
ORDER BY created_at DESC;
-- Delete by id
DELETE FROM wird_entries WHERE id = 42;
-- Correct a value
UPDATE wird_entries SET value = 350 WHERE id = 42;
#+END_SRC
If you want a delete button in the UI, the backend needs a ~DELETE~
endpoint:
#+BEGIN_SRC java
@DeleteMapping("/entries/{id}")
public ResponseEntity<Void> deleteEntry(@PathVariable Long id) {
repo.deleteById(id);
return ResponseEntity.noContent().build();
}
#+END_SRC
Then in the JS, add a delete button to each history row and call:
#+BEGIN_SRC javascript
await fetch(`${API}/entries/${id}`, { method: "DELETE" });
#+END_SRC
** Inspect data directly
The ~wird_daily_totals~ view is useful for quick summaries:
#+BEGIN_SRC sql
-- Today's totals
SELECT wird_type, total, log_count
FROM wird_daily_totals
WHERE date = CURRENT_DATE;
-- Last 7 days of durood
SELECT date, total
FROM wird_daily_totals
WHERE wird_type = 'durood'
AND date >= CURRENT_DATE - INTERVAL '7 days'
ORDER BY date DESC;
-- Check whether you met target each day
SELECT date, wird_type, total,
CASE WHEN total >= t.target THEN 'met' ELSE 'missed' END AS status
FROM wird_daily_totals w
JOIN wird_targets t USING (wird_type)
WHERE t.effective_from = (
SELECT MAX(effective_from) FROM wird_targets t2
WHERE t2.wird_type = w.wird_type
AND t2.effective_from <= w.date
)
ORDER BY date DESC, wird_type;
#+END_SRC
* Extensibility Notes
** Adding a weekly/monthly summary endpoint
The backend is structured to make this easy. Add a method to
~WirdEntryRepository~ using a ~@Query~ and expose it via a new
~@GetMapping~ in ~WirdController~. The JS can then call it and render
an additional panel without touching anything else.
** Making targets configurable via the DB
The ~wird_targets~ table already has ~effective_from~, which means you
can version targets over time. A query like the one in the last section
above shows the pattern for joining targets to entries correctly —
finding the most recent target that was in effect on a given date.
** Adding authentication
Currently the API has no auth — it is assumed the page is on a
personal/private site. If you ever need to restrict writes, the cleanest
approach given the existing Spring Boot setup is to add a simple API key
check in a ~HandlerInterceptor~ that only applies to ~POST~ and ~DELETE~
methods on ~/api/wird/~.
** Porting the frontend to a proper framework
The JS is a self-contained IIFE with no build step, which suits the
org-publish workflow. If you ever move to a build pipeline, the logic
maps cleanly onto a React component tree:
~<TodayPanel>~, ~<MeetingPanel>~, ~<TrendChart>~, ~<HistoryTable>~ — each
taking ~allEntries~ as a prop and deriving their state from it.
* Deployment Checklist
When you deploy changes, the steps depend on what you changed:
| Changed file | Action needed |
|------------------------+------------------------------------------------------------|
| ~wird-tracker.org~ | Re-run org-publish; the HTML will be regenerated |
| ~wird-tracker.css~ | Copy to ~static/css/~; hard-refresh browser cache |
| ~wird-tracker.js~ | Copy to ~static/js/~; hard-refresh browser cache |
| Any ~*.java~ file | Rebuild and restart the Spring Boot jar |
| SQL schema changes | Run the migration manually in psql; restart Spring Boot |
For CSS/JS cache busting during development, append a query string to
the ~<link>~ and ~<script>~ tags in the ~#+BEGIN_EXPORT html~ block:
#+BEGIN_SRC html
<link rel="stylesheet" href="/css/wird-tracker.css?v=2">
<script src="/js/wird-tracker.js?v=2"></script>
#+END_SRC

View File

@@ -2,7 +2,18 @@
#+OPTIONS: toc:nil num:nil
* Recently Updated (top 26 files - per lima's request)
- [[file:posts/career/restful-api.org][Restful API]] @@html:<span class="post-date">2026-03-08 17:23</span>@@
- [[file:home/wird-tracker.org][Wird Tracker]] @@html:<span class="post-date">2026-03-19 16:05</span>@@
- [[file:blogs/2026/03-march/setting-a-wird-tracker-19-03.org][Setting a Wird Tracker]] @@html:<span class="post-date">2026-03-19 13:57</span>@@
- [[file:index.org][Home Page]] @@html:<span class="post-date">2026-03-19 13:10</span>@@
- [[file:home/guide/wird-tracker-guide.org][Wird Tracker — Technical Guide]] @@html:<span class="post-date">2026-03-19 12:43</span>@@
- [[file:blogs/2026/03-march/fixing-the-dag-18-03.org][DAG fixes]] @@html:<span class="post-date">2026-03-19 10:42</span>@@
- [[file:blogs/2026/03-march/feeling-sleepy.org][Feeling extremely sleepy]] @@html:<span class="post-date">2026-03-19 10:41</span>@@
- [[file:blogs/2026/03-march/oversleeping-16-03.org][Oversleeping and missing a meeting...]] @@html:<span class="post-date">2026-03-16 12:17</span>@@
- [[file:blogs/2026/03-march/15-03-week-review.org][[15-03-2026] - Weekly Review]] @@html:<span class="post-date">2026-03-12 16:31</span>@@
- [[file:blogs/2026/03-march/08-03-week-review.org][[08-03-2026] - Weekly Review]] @@html:<span class="post-date">2026-03-12 16:17</span>@@
- [[file:posts/career/javascript.org][Understands the Javascript language]] @@html:<span class="post-date">2026-03-11 17:20</span>@@
- [[file:posts/career/ha-dr.org][High Availability, Disaster Recovery and Business Continuity]] @@html:<span class="post-date">2026-03-11 17:19</span>@@
- [[file:posts/career/restful-api.org][Restful API]] @@html:<span class="post-date">2026-03-08 17:37</span>@@
- [[file:home/status.org][Competency Status Board]] @@html:<span class="post-date">2026-03-08 16:28</span>@@
- [[file:blogs/2026/02-february/27-02-26.org][Journeys rambles again...]] @@html:<span class="post-date">2026-03-07 16:33</span>@@
- [[file:blogs/2026/03-march/01-03-week-review.org][[01-03-2026] - Weekly Review]] @@html:<span class="post-date">2026-03-07 12:45</span>@@
@@ -14,17 +25,6 @@
- [[file:blogs/2026/02-february/26-02-26.org][Starting the Journeys Upgrade]] @@html:<span class="post-date">2026-02-26 17:37</span>@@
- [[file:blogs/2026/02-february/24-02-26.org][Integration tests failing (sob)]] @@html:<span class="post-date">2026-02-24 17:00</span>@@
- [[file:home/backlog.org][Backlog]] @@html:<span class="post-date">2026-02-23 16:40</span>@@
- [[file:index.org][Home Page]] @@html:<span class="post-date">2026-02-23 12:37</span>@@
- [[file:blogs/2026/02-february/15-02-week-review.org][[15-02-2026] - Weekly Review]] @@html:<span class="post-date">2026-02-23 10:02</span>@@
- [[file:home/services.org][Service]] @@html:<span class="post-date">2026-02-11 13:26</span>@@
- [[file:blogs/2026/02-february/08-02-week-review.org][[08-02-2026] - Weekly Review]] @@html:<span class="post-date">2026-02-11 12:20</span>@@
- [[file:blogs/2026/02-february/01-02-week-review.org][[01-02-2026] - Weekly Review]] @@html:<span class="post-date">2026-02-11 12:20</span>@@
- [[file:blogs/2026/01-january/25-01-week-review.org][[25-01-2026] - Weekly Review]] @@html:<span class="post-date">2026-02-11 11:43</span>@@
- [[file:blogs/2026/01-january/18-01-week-review.org][[18-01-2026] - Weekly Review]] @@html:<span class="post-date">2026-01-18 23:05</span>@@
- [[file:blogs/2026/01-january/11-01-week-review.org][[11-01-2026] - Weekly Review]] @@html:<span class="post-date">2026-01-17 21:23</span>@@
- [[file:posts/career/airflow.org][Datamarts, Airflow and DAG's]] @@html:<span class="post-date">2026-01-17 21:17</span>@@
- [[file:blogs/2025/12-december/21-12-week-review.org][[21-12-2025] - Weekly Review]] @@html:<span class="post-date">2026-01-17 21:09</span>@@
- [[file:blogs/2025/12-december/28-12-week-review.org][[28-12-2025] - Weekly Review]] @@html:<span class="post-date">2026-01-17 21:08</span>@@
- [[file:blogs/2026/01-january/04-01-week-review.org][[04-01-2026] - Weekly Review]] @@html:<span class="post-date">2026-01-17 21:08</span>@@
- [[file:posts/career/probation-objectives.org][Probation Objectives:]] @@html:<span class="post-date">2026-01-17 21:06</span>@@
- [[file:posts/career/invest-principles.org][Invest Principles]] @@html:<span class="post-date">2026-01-04 17:15</span>@@

159
home/wird-tracker.org Normal file
View File

@@ -0,0 +1,159 @@
#+TITLE: Wird Tracker
#+OPTIONS: toc:nil num:nil
#+NO_SIDENOTES: t
#+COMMENTS: nil
#+SLUG: wird-tracker
This page tracks my daily awrād and spiritual practices.
#+BEGIN_EXPORT html
<div id="wird-app">
<!-- Today Panel -->
<section id="today-panel" class="wird-panel">
<div class="wird-panel-header">
<h2 id="today-date"></h2>
<div id="today-progress-wrap">
<div id="today-progress-bar"><div id="today-progress-fill"></div></div>
<span id="today-progress-label">0 / 5 complete</span>
</div>
</div>
<div id="wird-cards"></div>
</section>
<!-- Shaykh Meeting Panel -->
<section id="meeting-panel" class="wird-panel">
<div class="wird-panel-header">
<h2>Meeting with the Shaykh</h2>
<button id="log-meeting-btn" class="btn-primary">+ Log Meeting</button>
</div>
<div id="meeting-panel-body">
<p style="color:var(--muted);font-style:italic">Loading…</p>
</div>
</section>
<!-- Log Entry Modal -->
<dialog id="log-modal">
<form id="log-form" method="dialog">
<h3 id="modal-title">Log Entry</h3>
<div class="field-group">
<label for="log-date">Date</label>
<input type="date" id="log-date" required>
</div>
<div class="field-group">
<label for="log-type">Wird</label>
<select id="log-type" required>
<option value="durood">Durood</option>
<option value="istighfar">Istighfar</option>
<option value="quran">Qurʾān (juz)</option>
<option value="muraqabah">Murāqabah (min)</option>
<option value="wuqoof_qalbi">Wuqūf Qalbī</option>
<option value="shaykh_meeting" style="display:none">Meeting with Shaykh</option>
</select>
</div>
<!-- Numeric amount — count/juz/min wirds -->
<div class="field-group" id="value-group">
<label for="log-value">Amount</label>
<input type="number" id="log-value" min="0" step="0.5" placeholder="0">
</div>
<!-- Rating picker — wuqoof qalbi only -->
<div class="field-group" id="rating-group" style="display:none">
<label>Quality of presence</label>
<div class="modal-pips" id="modal-pips">
<button type="button" class="modal-pip" data-val="1">
<span class="pip-dot"></span>
<span class="pip-lbl">distracted</span>
</button>
<button type="button" class="modal-pip" data-val="2">
<span class="pip-dot"></span>
<span class="pip-lbl">scattered</span>
</button>
<button type="button" class="modal-pip" data-val="3">
<span class="pip-dot"></span>
<span class="pip-lbl">present</span>
</button>
<button type="button" class="modal-pip" data-val="4">
<span class="pip-dot"></span>
<span class="pip-lbl">attentive</span>
</button>
<button type="button" class="modal-pip" data-val="5">
<span class="pip-dot"></span>
<span class="pip-lbl">absorbed</span>
</button>
</div>
<input type="hidden" id="log-rating-value">
</div>
<!-- Shaykh meeting note -->
<div class="field-group" id="shaykh-group" style="display:none">
<p style="font-size:.82rem;color:var(--muted);margin:0">Set the date above to when the meeting took place.</p>
</div>
<div class="field-group">
<label for="log-notes">Notes <span class="optional">(optional)</span></label>
<textarea id="log-notes" rows="2" placeholder="Any reflections…"></textarea>
</div>
<div id="modal-actions">
<button type="button" id="modal-cancel">Cancel</button>
<button type="submit" id="modal-save" class="btn-primary">Save</button>
</div>
</form>
</dialog>
<!-- Trends -->
<section id="trends-panel" class="wird-panel">
<div class="wird-panel-header">
<h2>Trends</h2>
<div id="trend-controls">
<select id="trend-type">
<option value="durood">Durood</option>
<option value="istighfar">Istighfar</option>
<option value="quran">Qurʾān</option>
<option value="muraqabah">Murāqabah</option>
<option value="wuqoof_qalbi">Wuqūf Qalbī</option>
</select>
<select id="trend-range">
<option value="14">14 days</option>
<option value="30" selected>30 days</option>
<option value="90">90 days</option>
</select>
</div>
</div>
<canvas id="trend-chart"></canvas>
<div id="trend-stats"></div>
</section>
<!-- History -->
<section id="history-panel" class="wird-panel">
<div class="wird-panel-header">
<h2>History</h2>
<button id="open-log-btn" class="btn-primary">+ Log Entry</button>
</div>
<div id="history-table-wrap">
<table id="history-table">
<thead>
<tr>
<th>Date</th>
<th>Wird</th>
<th>Amount</th>
<th>vs Target</th>
<th>Notes</th>
</tr>
</thead>
<tbody id="history-body">
<tr><td colspan="5" class="loading-cell">Loading…</td></tr>
</tbody>
</table>
</div>
</section>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
#+END_EXPORT

View File

@@ -16,19 +16,13 @@ Feel free to explore:
- [[file:posts/career/career-list.org][Career Page]]
- [[file:blogs/2026/2026-list.org][2026]]
- [[file:blogs/2025/2025-list.org][2025]]
** Blogs and Posts
- [[file:blogs/blogs-list.org][Blogs Page]]
- [[file:posts/posts-list.org][Posts Page]]
** Setup Page
** Wird Page
- [[file:home/setup.org][Setup Page]]
- [[file:home/wird-tracker.org][Wird Page]]
** Sitemap
@@ -42,5 +36,5 @@ Feel free to explore:
- [[file:home/backlog.org][Backlog Page]]
- [[file:recently-updated.org][Recent Updates Page]]
- [[file:home/recently-updated.org][Recent Updates Page]]

View File

@@ -99,7 +99,7 @@ Defaults to N=30."
;; Trim
(setq items (cl-subseq items 0 (min count (length items))))
;; Write Org file
(with-temp-file (expand-file-name "recently-updated.org" z-org-root)
(with-temp-file (site-path "home/recently-updated.org")
(insert "#+TITLE: Recently Updated\n"
"#+OPTIONS: toc:nil num:nil\n\n"
"* Recently Updated (top 26 files - per lima's request)\n")

View File

@@ -81,6 +81,92 @@
"\n")))
(defun z/blogs-grouped-sitemap (title list)
"Sitemap grouped by year and month with dates and FILETAGS."
(let ((data (make-hash-table :test 'equal)))
;; STEP 1: Collect entries into (year -> month -> entries)
(dolist (entry (cdr list))
(when (consp entry)
(let* ((link (car entry))
(filename (if (string-match "\\[\\[file:\\([^]]+\\)\\]" link)
(match-string 1 link)
link))
(full-path (expand-file-name filename (site-path "blogs/")))
(date (when (file-exists-p full-path)
(org-publish-find-date full-path org-publish-project-alist))))
(when date
(let* ((year (format-time-string "%Y" date))
(month (format-time-string "%B %Y" date))
(date-str (format-time-string "%d-%m-%Y %H:%M" date))
(tags-str ""))
;; Get tags
(when (file-exists-p full-path)
(with-temp-buffer
(insert-file-contents full-path)
(org-mode)
(let ((tags (cadr (assoc "FILETAGS"
(org-collect-keywords '("FILETAGS"))))))
(when tags
(setq tags-str
(mapconcat
(lambda (tag)
(format "@@html:<a href=\"/tags/%s.html\"> \
<span class=\"post-tag\">%s</span> </a>@@" tag tag))
(split-string tags ":" t)
" "))))))
;; Insert into hash table
(let ((year-table (or (gethash year data)
(puthash year (make-hash-table :test 'equal) data))))
(let ((month-list (gethash month year-table)))
(puthash month
(cons (list link date-str tags-str date)
month-list)
year-table))))))))
;; STEP 2: Render output
(let ((output (concat
"#+TITLE: " title "\n"
"#+OPTIONS: toc:nil num:nil \n\n"
"See the categories: @@html:<a href=\"../home/categories.html\">Categories</a>@@\n\n")))
;; Sort years descending
(dolist (year (sort (hash-table-keys data) #'string>))
(setq output (concat output "* " year "\n"))
(let ((year-table (gethash year data)))
;; Sort months by actual date (descending)
(dolist (month
(sort (hash-table-keys year-table)
(lambda (a b)
(time-less-p
(date-to-time (concat "01 " b))
(date-to-time (concat "01 " a))))))
(setq output (concat output "\n** " month "\n"))
;; Sort entries by date descending
(dolist (entry
(sort (gethash month year-table)
(lambda (a b)
(time-less-p (nth 3 b) (nth 3 a)))))
(let ((link (nth 0 entry))
(date-str (nth 1 entry))
(tags-str (nth 2 entry)))
(setq output
(concat output
(format "- %s @@html:<span class=\"post-date\">%s</span>@@ %s\n"
link date-str tags-str))))))))
output)))
(defun z/books-sitemap (title list)
"sitemap that lists books."
(concat

File diff suppressed because it is too large Load Diff

View File

@@ -5,6 +5,10 @@ See the categories: @@html:<a href="../../home/categories.html">Categories</a>@@
See the following page for more details: @@html:<a href="./career-intro.html">Career Intro</a>@@
** March 2026
- [[file:ha-dr.org][High Availability, Disaster Recovery and Business Continuity]] @@html:<span class="post-date">11-03-2026 17:18</span>@@ @@html:<a href="/tags/learning.html"> <span class="post-tag">learning</span> </a>@@ @@html:<a href="/tags/notes.html"> <span class="post-tag">notes</span> </a>@@
- [[file:javascript.org][Understands the Javascript language]] @@html:<span class="post-date">11-03-2026 16:52</span>@@ @@html:<a href="/tags/learning.html"> <span class="post-tag">learning</span> </a>@@ @@html:<a href="/tags/notes.html"> <span class="post-tag">notes</span> </a>@@
** February 2026
- [[file:restful-api.org][Restful API]] @@html:<span class="post-date">15-02-2026 23:00</span>@@ @@html:<a href="/tags/learning.html"> <span class="post-tag">learning</span> </a>@@ @@html:<a href="/tags/notes.html"> <span class="post-tag">notes</span> </a>@@

87
posts/career/ha-dr.org Normal file
View File

@@ -0,0 +1,87 @@
#+TITLE: High Availability, Disaster Recovery and Business Continuity
#+OPTIONS: num:nil
#+DATE: <2026-03-11 Wed 17:18>
#+filetags: :learning:notes:
#+WIP:
#+COMMENTS: t
#+SLUG: high-availability-disaster-recovery
* HA, DR and BC Competency Log
Understanding High Availability (HA), Disaster Recovery (DR), and
Business Continuity Planning (BCP) is important when designing reliable
systems and analysing service incidents. These concepts help Microlise
(and organisations in general) to minimise downtime, recover from
failures, and maintain service availability.
*** High Availability (HA)
High Availability focuses on preventing service disruption by using
redundancy. Systems are designed with backup components so that if one
fails, another can take over automatically.
Examples include:
- Redundant servers or network paths
- Load balancing across multiple systems
- Automatic failover modes
This ensures services continue operating even when individual components
fail. We have this at Microlise where if there is a failure on one of
the data centres, we can failover to the other one.
An analogy from the article compares this to a bicycle with two brakes.
The bike can operate with only one brake, but having two provides
redundancy in case one fails.
*** Disaster Recovery (DR)
Disaster Recovery focuses on restoring systems after a major failure.
This includes recovering infrastructure, applications, and data so
services can resume operation.
Some of the common DR tools and methods include:
- Offsite backups
- Replicated environments (e.g., multiple data centres with sync)
- Recovery procedures and restoration tools
Unlike High Availability, which tries to prevent downtime, DR assumes a
failure has already occurred. In the bicycle analogy, after the crash
the rider takes a bus home and then drives to work to complete their
important task. This represents a recovery process after a major
disruption.
*** Business Continuity Planning (BCP)
BCP ensures the business can continue operating during or after a
disruption. This may involve alternative systems, temporary processes,
or backup locations to keep services running.
In the analogy, the car at home represents BCP because it allows the
rider to continue their journey despite the broken bicycle. A Business
Impact Assessment (BIA) helps determine the priority of services and the
acceptable level of downtime.
*** RTO and RPO
Two key recovery metrics are:
- Recovery Time Objective (RTO): The maximum time allowed to restore a
service after failure. For example, if a critical service must be
restored within 4 hours, the disaster recovery process must ensure
systems are operational within that timeframe.
- Recovery Point Objective (RPO): The maximum acceptable amount of data
loss.
RPO example:
- An RPO of 0 minutes means no data loss is acceptable.
- An RPO of 1 hour means up to one hour of data could be lost.
These values help define the required level of HA and DR design.
*** Application in Real Scenarios
These concepts are useful when performing incident debugging, root cause
analysis, or explaining service issues to customer support teams. For
example, engineers may check whether failover worked correctly, whether
recovery met the RTO, or whether backups allowed data to be restored
within the RPO.
In summary, the reason why we need to understand HA, DR, and BCP is to
maintain service availability and ensure business continuity.

View File

@@ -0,0 +1,92 @@
#+TITLE: Understands the Javascript language
#+OPTIONS: num:nil
#+DATE: <2026-03-11 Wed 16:52>
#+filetags: :learning:notes:
#+WIP:
#+COMMENTS: t
#+SLUG: understands-the-javascript-language
* JS - Competency Log
To evidence this competency I wanted to use a comment system that I
implemented for my website. The goal was to fetch comments from an API,
render them dynamically and support nested replies.
** Working with the DOM
A key part of front end JS development is manipulating the DOM (Document
Object Model). I dynamically created comment elements using
=document.createElement= rather than injecting raw HTML. This approach
avoids security issues such as cross-site scripting (XSS).
As an example:
#+begin_src js
const wrapper = document.createElement("div");
wrapper.className = "comment";
const author = document.createElement("strong");
author.textContent = comment.author || "Anonymous";
#+end_src
Using textContent ensures that any user submitted content is safely
rendered as text instead of HTML.
** Data structures
Comments returned from the backend are stored as a flat array, but
replies must be displayed as a nested tree structure. To solve this, I
implemented the buildCommentTree function.
#+begin_src js
const byId = {};
const roots = [];
#+end_src
I use the JS object =byId= as a lookup table (O(1) complexity) to access
comments by ID. This ensures that I can quickly find the parent of a
comment. I then use the roots array to store the top-level comments,
which are the ones without a parent ID.
#+begin_src js
const parent = byId[comment.parent_id];
if (parent) parent.children.push(comment);
#+end_src
** Async programming
Modern front-end applications frequently communicate with APIs. I used
async/await to handle asynchronous operations when fetching and posting
comments.
#+begin_src js
async function fetchComments() {
const res = await fetch(`/api/comments/${pageSlug}`);
return await res.json();
}
#+end_src
I used async/await instead of promise chaining to make the code more
readable and easier to understand. I also added some response
validations:
#+begin_src js
if (!res.ok) {
console.error("Failed to fetch comments");
}
#+end_src
** Event Handling
JS event handling was used to respond to user interactions, such as
submitting a comment or replying.
#+begin_src js
form.addEventListener("submit", async event => {
event.preventDefault();
#+end_src
Calling preventDefault() prevents the browser from reloading the page
during form submission, allowing the comment system to update
dynamically instead.
Also the same for replies:
#+begin_src js
replyBtn.onclick = () => showReplyForm(wrapper, Number(comment.id));
#+end_src

View File

@@ -4,7 +4,9 @@
See the categories: @@html:<a href="../home/categories.html">Categories</a>@@
* Posts:
- [[file:career/career-list.org][Career List]] @@html:<span class="post-date">08-03-2026 17:34</span>@@
- [[file:career/career-list.org][Career List]] @@html:<span class="post-date">19-03-2026 16:17</span>@@
- [[file:career/ha-dr.org][High Availability, Disaster Recovery and Business Continuity]] @@html:<span class="post-date">11-03-2026 17:18</span>@@ @@html:<a href="/tags/learning.html"> <span class="post-tag">learning</span> </a>@@ @@html:<a href="/tags/notes.html"> <span class="post-tag">notes</span> </a>@@
- [[file:career/javascript.org][Understands the Javascript language]] @@html:<span class="post-date">11-03-2026 16:52</span>@@ @@html:<a href="/tags/learning.html"> <span class="post-tag">learning</span> </a>@@ @@html:<a href="/tags/notes.html"> <span class="post-tag">notes</span> </a>@@
- [[file:career/restful-api.org][Restful API]] @@html:<span class="post-date">15-02-2026 23:00</span>@@ @@html:<a href="/tags/learning.html"> <span class="post-tag">learning</span> </a>@@ @@html:<a href="/tags/notes.html"> <span class="post-tag">notes</span> </a>@@
- [[file:career/database-permissions.org][Database Permissions, Roles, and Accounts]] @@html:<span class="post-date">18-01-2026 23:00</span>@@ @@html:<a href="/tags/learning.html"> <span class="post-tag">learning</span> </a>@@ @@html:<a href="/tags/notes.html"> <span class="post-tag">notes</span> </a>@@
- [[file:career/monitoring-and-logging.org][Monitoring and Logging]] @@html:<span class="post-date">18-01-2026 23:00</span>@@ @@html:<a href="/tags/learning.html"> <span class="post-tag">learning</span> </a>@@ @@html:<a href="/tags/notes.html"> <span class="post-tag">notes</span> </a>@@

View File

@@ -2,16 +2,6 @@
- [[file:index.org][Home Page]]
- [[file:wip.org][Work in progress]]
- [[file:recently-updated.org][Recently Updated]]
- home
- [[file:home/countdown.org][Countdown]]
- [[file:home/contact.org][Contact]]
- [[file:home/backlog.org][Backlog]]
- [[file:home/setup.org][Setup]]
- [[file:home/notes.org][Notes]]
- [[file:home/services.org][Service]]
- [[file:home/status.org][Competency Status Board]]
- [[file:home/categories.org][Categories]]
- posts
- [[file:posts/posts-intro.org][Posts Introduction]]
- [[file:posts/posts-list.org][Posts List]]
@@ -32,50 +22,13 @@
- [[file:posts/career/monitoring-and-logging.org][Monitoring and Logging]]
- [[file:posts/career/pipelines.org][Pipelines and how they work (as well as CI/CD)]]
- [[file:posts/career/restful-api.org][Restful API]]
- [[file:posts/career/javascript.org][Understands the Javascript language]]
- [[file:posts/career/ha-dr.org][High Availability, Disaster Recovery and Business Continuity]]
- [[file:posts/career/career-list.org][Career List]]
- blogs
- [[file:blogs/blogs-intro.org][Blogs Introduction]]
- [[file:blogs/publish-pages.org][How to publish pages using Org Publish]]
- [[file:blogs/blogs-list.org][Blogs List]]
- 2025
- [[file:blogs/2025/2025-list.org][2025 List]]
- 11-november
- [[file:blogs/2025/11-november/02-11-week-review.org][[02-11-2025] - Weekly Review]]
- [[file:blogs/2025/11-november/09-11-week-review.org][[09-11-2025] - Weekly Review]]
- [[file:blogs/2025/11-november/16-11-week-review.org][[16-11-2025] - Weekly Review]]
- [[file:blogs/2025/11-november/23-11-week-review.org][[23-11-2025] - Weekly Review]]
- [[file:blogs/2025/11-november/30-11-week-review.org][[30-11-2025] - Weekly Review]]
- 08-august
- [[file:blogs/2025/08-august/zettelkasten.org][Zettelkasten Method]]
- [[file:blogs/2025/08-august/what-do-i-want-to-do-with-emacs.org][What Do I Want To Do With Emacs]]
- [[file:blogs/2025/08-august/wacom-with-arch.org][Wacom With Arch]]
- [[file:blogs/2025/08-august/spending-the-whole-day-on-this-website.org][09-08-2025: Website Changes]]
- [[file:blogs/2025/08-august/hilberts.hotel.org][Hilbert's Hotel]]
- [[file:blogs/2025/08-august/benefits-of-reading.org][Benefits of Reading]]
- [[file:blogs/2025/08-august/third-time.org][Third Time]]
- 12-december
- [[file:blogs/2025/12-december/07-12-week-review.org][[07-12-2025] - Weekly Review]]
- [[file:blogs/2025/12-december/14-12-week-review.org][[14-12-2025] - Weekly Review]]
- [[file:blogs/2025/12-december/21-12-week-review.org][[21-12-2025] - Weekly Review]]
- [[file:blogs/2025/12-december/28-12-week-review.org][[28-12-2025] - Weekly Review]]
- 2026
- [[file:blogs/2026/2026-list.org][2026 List]]
- 01-january
- [[file:blogs/2026/01-january/04-01-week-review.org][[04-01-2026] - Weekly Review]]
- [[file:blogs/2026/01-january/11-01-week-review.org][[11-01-2026] - Weekly Review]]
- [[file:blogs/2026/01-january/18-01-week-review.org][[18-01-2026] - Weekly Review]]
- [[file:blogs/2026/01-january/25-01-week-review.org][[25-01-2026] - Weekly Review]]
- 03-march
- [[file:blogs/2026/03-march/01-03-week-review.org][[01-03-2026] - Weekly Review]]
- 02-february
- [[file:blogs/2026/02-february/01-02-week-review.org][[01-02-2026] - Weekly Review]]
- [[file:blogs/2026/02-february/third-meeting.org][Third Meeting with lima :)]]
- [[file:blogs/2026/02-february/08-02-week-review.org][[08-02-2026] - Weekly Review]]
- [[file:blogs/2026/02-february/15-02-week-review.org][[15-02-2026] - Weekly Review]]
- [[file:blogs/2026/02-february/22-02-week-review.org][[22-02-2026] - Weekly Review]]
- [[file:blogs/2026/02-february/24-02-26.org][Integration tests failing (sob)]]
- [[file:blogs/2026/02-february/26-02-26.org][Starting the Journeys Upgrade]]
- [[file:blogs/2026/02-february/27-02-26.org][Journeys rambles again...]]
- books
- [[file:books/books-list.org][Books List]]
- clean-code
@@ -87,10 +40,23 @@
- [[file:tags/learning.org][Tag: learning]]
- [[file:tags/notes.org][Tag: notes]]
- [[file:tags/review.org][Tag: review]]
- [[file:tags/website.org][Tag: website]]
- [[file:tags/education.org][Tag: education]]
- [[file:tags/insights.org][Tag: insights]]
- [[file:tags/life.org][Tag: life]]
- [[file:tags/website.org][Tag: website]]
- [[file:tags/emacs.org][Tag: emacs]]
- [[file:tags/maths.org][Tag: maths]]
- [[file:tags/education.org][Tag: education]]
- [[file:tags/reading.org][Tag: reading]]
- [[file:tags/maths.org][Tag: maths]]
- home
- [[file:home/countdown.org][Countdown]]
- [[file:home/contact.org][Contact]]
- [[file:home/backlog.org][Backlog]]
- [[file:home/notes.org][Notes]]
- [[file:home/services.org][Service]]
- [[file:home/status.org][Competency Status Board]]
- [[file:home/wird-tracker.org][Wird Tracker]]
- [[file:home/categories.org][Categories]]
- [[file:home/recently-updated.org][Recently Updated]]
- guide
- [[file:home/guide/setup.org][Setup]]
- [[file:home/guide/wird-tracker-guide.org][Wird Tracker — Technical Guide]]

View File

@@ -2,6 +2,8 @@
#+OPTIONS: toc:nil num:nil title:nil
* Posts tagged learning
- [[file:../posts/career/ha-dr.org][High Availability, Disaster Recovery and Business Continuity]]
- [[file:../posts/career/javascript.org][Understands the Javascript language]]
- [[file:../posts/career/restful-api.org][Restful API]]
- [[file:../posts/career/database-permissions.org][Database Permissions, Roles, and Accounts]]
- [[file:../posts/career/monitoring-and-logging.org][Monitoring and Logging]]

View File

@@ -2,6 +2,10 @@
#+OPTIONS: toc:nil num:nil title:nil
* Posts tagged life
- [[file:../blogs/2026/03-march/setting-a-wird-tracker-19-03.org][Setting a Wird Tracker]]
- [[file:../blogs/2026/03-march/fixing-the-dag-18-03.org][DAG fixes]]
- [[file:../blogs/2026/03-march/feeling-sleepy.org][Feeling extremely sleepy]]
- [[file:../blogs/2026/03-march/oversleeping-16-03.org][Oversleeping and missing a meeting...]]
- [[file:../blogs/2026/02-february/27-02-26.org][Journeys rambles again...]]
- [[file:../blogs/2026/02-february/26-02-26.org][Starting the Journeys Upgrade]]
- [[file:../blogs/2026/02-february/24-02-26.org][Integration tests failing (sob)]]

View File

@@ -2,6 +2,8 @@
#+OPTIONS: toc:nil num:nil title:nil
* Posts tagged notes
- [[file:../posts/career/ha-dr.org][High Availability, Disaster Recovery and Business Continuity]]
- [[file:../posts/career/javascript.org][Understands the Javascript language]]
- [[file:../posts/career/restful-api.org][Restful API]]
- [[file:../posts/career/database-permissions.org][Database Permissions, Roles, and Accounts]]
- [[file:../posts/career/monitoring-and-logging.org][Monitoring and Logging]]

View File

@@ -2,6 +2,8 @@
#+OPTIONS: toc:nil num:nil title:nil
* Posts tagged review
- [[file:../blogs/2026/03-march/15-03-week-review.org][[15-03-2026] - Weekly Review]]
- [[file:../blogs/2026/03-march/08-03-week-review.org][[08-03-2026] - Weekly Review]]
- [[file:../blogs/2026/03-march/01-03-week-review.org][[01-03-2026] - Weekly Review]]
- [[file:../blogs/2026/02-february/22-02-week-review.org][[22-02-2026] - Weekly Review]]
- [[file:../blogs/2026/02-february/15-02-week-review.org][[15-02-2026] - Weekly Review]]