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

@@ -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

30
home/recently-updated.org Executable file
View File

@@ -0,0 +1,30 @@
#+TITLE: Recently Updated
#+OPTIONS: toc:nil num:nil
* Recently Updated (top 26 files - per lima's request)
- [[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>@@
- [[file:posts/career/database-permissions.org][Database Permissions, Roles, and Accounts]] @@html:<span class="post-date">2026-03-05 13:06</span>@@
- [[file:posts/career/monitoring-and-logging.org][Monitoring and Logging]] @@html:<span class="post-date">2026-03-05 12:58</span>@@
- [[file:posts/career/pipelines.org][Pipelines and how they work (as well as CI/CD)]] @@html:<span class="post-date">2026-03-05 11:52</span>@@
- [[file:blogs/2026/02-february/third-meeting.org][Third Meeting with lima :)]] @@html:<span class="post-date">2026-03-03 12:02</span>@@
- [[file:blogs/2026/02-february/22-02-week-review.org][[22-02-2026] - Weekly Review]] @@html:<span class="post-date">2026-02-27 17:07</span>@@
- [[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: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>@@

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