Triangular affect journaling. Tap a point inside a triangle to log where you are on the good / bad / naivete spectrum and track patterns over time.
The three vertices map to actualfreedom.com.au's categorization of feelings:
- Sensuous (green, top) — felicitous and innocuous feelings (delightful, harmonious)
- 'Bad' (red, bottom-left) — hostile and invidious passions (hateful, fearful)
- 'Good' (pink, bottom-right) — affectionate and desirable passions (loving, trusting)
Your tap position is stored as barycentric coordinates — three values ∈ [0,1] that sum to 1, representing proximity to each vertex.
Tap the triangle to log a mood instantly (with haptic feedback on mobile). Today's entries appear as colored dots on the triangle, and past week averages show as faded dots behind them.
Three switchable views for today's entries (persisted in localStorage):
- Timeline — horizontal 24h bar with dots at their logged time
- Dots — chronological dot row, pure mood sequence
- Arcs — stacked semicircle with average mood mini-triangle in the center
Tap any calendar day to view its entries in the same views.
A 4-week heatmap with mini mood-triangles per day and a consecutive-day streak counter.
A static build is deployed to GitHub Pages on every push to master. All data lives in your browser's IndexedDB via Triplit — nothing leaves the device. No server, no account, no sync.
Tap Export data at the bottom of the page to download all entries as a JSON file (triffect-YYYY-MM-DD.json). Tap Import data to restore entries from a previously exported file. Import is idempotent — duplicate entries (matched by ID) are skipped.
The export format is a versioned envelope:
{
"version": 1,
"exportedAt": "2026-03-30T10:15:00.000Z",
"entries": [
{
"id": "01JQXYZ1234567890ABCDEF",
"good": 0.33,
"bad": 0.33,
"naivete": 0.34,
"created_at": "2026-03-30T09:00:00.000Z"
}
]
}Each entry stores a point in the triangle:
| Field | Type | Description |
|---|---|---|
id |
string (auto) | Unique identifier |
good |
number [0,1] | Barycentric weight toward Good vertex |
bad |
number [0,1] | Barycentric weight toward Bad vertex |
naivete |
number [0,1] | Barycentric weight toward Naivete vertex |
created_at |
date | Timestamp of the entry |
The three barycentric coordinates always sum to 1. A tap near the Naivete vertex produces high naivete, low good and bad.
| Layer | Choice |
|---|---|
| UI | SolidJS + TailwindCSS v4 |
| Data | Triplit — local-first with IndexedDB, auto-sync to self-hosted server |
| Build | Vite + vite-plugin-pwa |
| Tests | Cucumber + Playwright |
| Dev env | Nix flake (zero-input, npins) |
| CI | just ci — distributed across systems via SSH, posts GitHub commit statuses |
nix develop # or: direnv allow
just install
just dev # starts Vite + Triplit dev serverA home-manager module is provided for NixOS deployment behind Tailscale or equivalent. No auth — single-user by design.
services.triffect = {
enable = true;
package = triffect.packages.x86_64-linux.default;
port = 8080;
};just test # build + e2e (Cucumber/Playwright)
just test-dev # e2e against running dev serverjust ci # runs nix build + home-manager + e2e on all configured systems
just ci protect # sets up GitHub branch protection from CI checks- actualfreedom.com.au — the affect categorization this app is based on
- Triplit docs — local-first database with sync
- Barycentric coordinates — the coordinate system used for triangle positions