297 lines
13 KiB
Markdown
297 lines
13 KiB
Markdown
|
|
# Action Points — Interactive Checklist PRD
|
|||
|
|
|
|||
|
|
**Status:** Draft
|
|||
|
|
**Author:** aja + Hermes
|
|||
|
|
**Date:** 2026-06-03
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 1. Problem Statement
|
|||
|
|
|
|||
|
|
The Tree of Ascension site contains practices and strategies that invite the reader to *do* something — meditate, journal, take a walk barefoot, migrate off a service. Right now those calls to action are flat text. A reader who returns tomorrow has no way to see what they've already tried or what remains.
|
|||
|
|
|
|||
|
|
We want to make the site *participatory*: let readers check off action items, see their progress, and feel a sense of momentum. Completion should be visible and celebratory — not gamified in a manipulative way, but genuinely satisfying.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 2. Feature Overview
|
|||
|
|
|
|||
|
|
| # | Feature | Summary |
|
|||
|
|
|---|---------|---------|
|
|||
|
|
| 1 | **Checkable action points** | Markdown `- [ ]` task lists that are clickable and persist checked state in `localStorage` |
|
|||
|
|
| 2 | **Heading counters** | Each heading with action items underneath shows a counter (e.g., `3/5`). When all items are checked, the heading changes color and confetti drops from the top of the page |
|
|||
|
|
| 3 | **Navigation badges** | Sidebar navigation items show a checkmark and color change when all action items on that page are checked |
|
|||
|
|
| 4 | **Page-level completion** | The top-of-page heading (h1) reflects overall page completion with a counter |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 3. Detailed Specifications
|
|||
|
|
|
|||
|
|
### 3.1 Checkable Action Points
|
|||
|
|
|
|||
|
|
**What they are:**
|
|||
|
|
Any `- [ ]` Markdown task list item rendered on a page. The site already uses `pymdownx.tasklist` with `custom_checkbox = true`, which renders checkboxes. We layer interactivity + persistence on top.
|
|||
|
|
|
|||
|
|
**Behavior:**
|
|||
|
|
- Clicking a checkbox toggles its state (unchecked ↔ checked)
|
|||
|
|
- State is immediately persisted to `localStorage`
|
|||
|
|
- On page load (and on instant navigation), previously saved state is restored
|
|||
|
|
- If a checkbox's label text no longer matches any saved entry (content was edited), the stale entry is silently ignored
|
|||
|
|
|
|||
|
|
**Checkbox identification:**
|
|||
|
|
Each checkbox is identified by a key derived from:
|
|||
|
|
1. The page's canonical URL path (e.g., `/practices/daily-sadhana/`)
|
|||
|
|
2. The normalized label text (trimmed, lowercased, collapsed whitespace)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
Key = URL + "::" + normalized_label
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
This is stable across reloads and survives minor whitespace changes but intentionally breaks on substantial rewrites (which should reset progress anyway).
|
|||
|
|
|
|||
|
|
**Visual style:**
|
|||
|
|
- Unchecked: current Zensical tasklist styling (green-tinted)
|
|||
|
|
- Checked: strikethrough + muted color, consistent with existing site palette
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 3.2 Heading Counters & Completion Celebration
|
|||
|
|
|
|||
|
|
**Counter display:**
|
|||
|
|
For each heading (h2, h3, h4) that has one or more action items in its scope, a pill badge is floated to the right of the heading:
|
|||
|
|
```
|
|||
|
|
## Meditation [0/2]
|
|||
|
|
```
|
|||
|
|
When partially complete:
|
|||
|
|
```
|
|||
|
|
## Meditation [1/2]
|
|||
|
|
```
|
|||
|
|
When fully complete — the heading and badge both shift to the site's accent aurora purple (`--gp-accent`).
|
|||
|
|
|
|||
|
|
**Heading scope:**
|
|||
|
|
An action item belongs to the nearest preceding heading at or above its level. Concretely:
|
|||
|
|
- Items after an h2 but before the next h2 belong to that h2
|
|||
|
|
- Items after an h3 but before the next h2 or h3 belong to that h3
|
|||
|
|
|
|||
|
|
**Confetti trigger:**
|
|||
|
|
When the last unchecked item under a heading is checked (counter goes from `(n-1)/n` to `n/n`):
|
|||
|
|
- A confetti burst animates from the top of the viewport
|
|||
|
|
- Confetti uses the site's palette (greens, aurora purple, soft gold)
|
|||
|
|
- Animation lasts ~3 seconds
|
|||
|
|
- Respects `prefers-reduced-motion` (skips entirely)
|
|||
|
|
|
|||
|
|
**Confetti implementation:** Custom canvas animation (zero dependency).
|
|||
|
|
Two tiers:
|
|||
|
|
- **Heading completion:** Subtle aurora particle shower — ~2s, calm, particles in site palette. Accompanied by a soft 432 Hz sine chime (Web Audio API, gentle attack/decay envelope).
|
|||
|
|
- **Page-level completion:** More energetic burst — ~3s, denser particles. Accompanied by an ascending arpeggio centered on 528 Hz (Web Audio API, sine waves with smooth envelope).
|
|||
|
|
|
|||
|
|
Both respect `prefers-reduced-motion` (confetti skipped, sounds skipped).
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 3.3 Navigation Integration
|
|||
|
|
|
|||
|
|
**Sidebar nav items:**
|
|||
|
|
Each link in the left sidebar that points to a page with action items:
|
|||
|
|
- Displays a pill badge next to the link text showing the completion count (e.g., `2/5`)
|
|||
|
|
- When all items on that page are complete, the badge becomes a ✓ checkmark and the link text shifts to the accent color
|
|||
|
|
|
|||
|
|
**Index page aggregation:**
|
|||
|
|
Section index pages (e.g., `/practices/`) aggregate completion across all their sub-pages. The badge shows the combined total — e.g., `12/28` summing all practice pages. Sub-page grouping is derived from URL structure: `/practices/daily-sadhana/` is a child of `/practices/`.
|
|||
|
|
|
|||
|
|
**How it works:**
|
|||
|
|
1. After a page renders and checkboxes are initialized, compute `{ checked, total }` for that page
|
|||
|
|
2. Store the summary in `localStorage` under the page URL
|
|||
|
|
3. Find the corresponding `<a>` element in the sidebar navigation and inject the badge/checkmark
|
|||
|
|
4. Re-run on every page navigation (instant or full)
|
|||
|
|
|
|||
|
|
**Edge cases:**
|
|||
|
|
- Pages with no action items: no badge, no color change
|
|||
|
|
- Deleted pages: stale localStorage entries don't render (no nav element to match)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
### 3.4 Page-Level Heading
|
|||
|
|
|
|||
|
|
The top-of-page `<h1>` gets a pill badge (right-aligned) showing overall page progress:
|
|||
|
|
```
|
|||
|
|
# Daily Sadhana [4/12]
|
|||
|
|
```
|
|||
|
|
Same color-change logic as section headings: when complete, the h1 turns accent-colored.
|
|||
|
|
|
|||
|
|
The h1 counter aggregates *all* action items on the page — it's the sum of all section counts.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 3.5 Progress Export (trivial add-on)
|
|||
|
|
|
|||
|
|
A small "Download my progress" link in the page footer or near the comments section. Clicking it triggers a browser download of a JSON file containing the full localStorage state. This lets users:
|
|||
|
|
- Back up their progress before clearing browser data
|
|||
|
|
- Share their completion state (import could come later)
|
|||
|
|
|
|||
|
|
Implementation is ~10 lines: read `localStorage`, create a `Blob`, trigger download via a temporary `<a>` element.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 4. Technical Design
|
|||
|
|
|
|||
|
|
### 4.1 localStorage Schema
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"toa-action-points": {
|
|||
|
|
"version": 1,
|
|||
|
|
"pages": {
|
|||
|
|
"/practices/daily-sadhana/": {
|
|||
|
|
"lastUpdated": "2026-06-03T12:00:00.000Z",
|
|||
|
|
"checked": [
|
|||
|
|
"sit. breathe. that's the whole instruction.",
|
|||
|
|
"write down three things you are genuinely grateful for"
|
|||
|
|
]
|
|||
|
|
},
|
|||
|
|
"/strategies/digital-sovereignty/": {
|
|||
|
|
"lastUpdated": "2026-06-03T12:30:00.000Z",
|
|||
|
|
"checked": [
|
|||
|
|
"switch from wifi to ethernet for your main computer"
|
|||
|
|
]
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- Keys are the site-relative URL path (without domain; works across environments)
|
|||
|
|
- `checked` is an array of normalized label strings
|
|||
|
|
- `version` allows future schema migrations
|
|||
|
|
- One key per page keeps reads/writes small
|
|||
|
|
|
|||
|
|
### 4.2 Instant Navigation Compatibility
|
|||
|
|
|
|||
|
|
The site uses `navigation.instant` (SPA-style page transitions). Zensical/Material for MkDocs exposes `document$` as an observable for navigation events.
|
|||
|
|
|
|||
|
|
Our JS hooks into:
|
|||
|
|
- `DOMContentLoaded` — for initial page load
|
|||
|
|
- `document$.subscribe` — for instant navigation transitions
|
|||
|
|
|
|||
|
|
On each navigation:
|
|||
|
|
1. Wait for DOM to settle (the new page content is rendered)
|
|||
|
|
2. Scan for tasklist items
|
|||
|
|
3. Restore saved state from localStorage
|
|||
|
|
4. Inject counters into headings and h1
|
|||
|
|
5. Update sidebar nav badges for *all* pages (not just current — because the current page's completion may have changed)
|
|||
|
|
|
|||
|
|
### 4.3 Storage Limit
|
|||
|
|
|
|||
|
|
`localStorage` is typically 5–10 MB per origin. Our schema is compact (a few KB even with many pages). No practical risk of hitting the limit.
|
|||
|
|
|
|||
|
|
### 4.4 Performance
|
|||
|
|
|
|||
|
|
- DOM scanning on each navigation: O(n) where n = number of tasklist items on a page (typically < 30)
|
|||
|
|
- Nav badge updates: O(sidebar links × pages with data), well under 1ms
|
|||
|
|
- confetti: canvas-based, runs off main thread via `requestAnimationFrame`
|
|||
|
|
|
|||
|
|
All work is < 5ms per navigation; no perceptible impact.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 5. Future: Cross-Device Persistence (v2)
|
|||
|
|
|
|||
|
|
### 5.1 Problem
|
|||
|
|
`localStorage` is per-browser, per-device. A reader who checks items on their phone won't see them on their laptop.
|
|||
|
|
|
|||
|
|
### 5.2 Solution sketch
|
|||
|
|
- User optionally enters their email address
|
|||
|
|
- We store a mapping: `email → checked_items` on a lightweight server
|
|||
|
|
- On page load, if an email is stored in localStorage, fetch state from the server
|
|||
|
|
- On checkbox toggle, sync to server (debounced)
|
|||
|
|
- No authentication required — email is the identifier (like Cusdis comments)
|
|||
|
|
- Server can be a simple SQLite-backed Python/FastAPI service or even a Cloudflare Worker + D1
|
|||
|
|
|
|||
|
|
### 5.3 Why not now
|
|||
|
|
- Requires deploying and maintaining a server
|
|||
|
|
- Adds latency and failure modes
|
|||
|
|
- `localStorage` solves 80% of the value with 0% of the infrastructure cost
|
|||
|
|
- We ship v1, learn from usage, then decide if cross-device is worth it
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 6. Content Authoring Guide
|
|||
|
|
|
|||
|
|
For content authors, "action points" are simply `- [ ]` Markdown task lists. No special syntax, no frontmatter, no directives.
|
|||
|
|
|
|||
|
|
```markdown
|
|||
|
|
## Meditation
|
|||
|
|
|
|||
|
|
- [ ] Sit quietly for 5 minutes
|
|||
|
|
- [ ] Notice when the mind wanders and return to breath
|
|||
|
|
- [ ] Do this every morning for one week
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
The JS layer automatically detects these and adds interactivity. To opt a page *out* of action-point tracking entirely (e.g., index pages with task lists that aren't meant to be checked off), add frontmatter:
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
---
|
|||
|
|
action_points: false
|
|||
|
|
---
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 7. Open Questions
|
|||
|
|
|
|||
|
|
1. **Confetti style — subtle or playful?**
|
|||
|
|
A gentle aurora-colored particle shower that respects the site's calm aesthetic, or a more energetic celebration? The site's ethos ("not gamified in a manipulative way, but genuinely satisfying") points toward subtle — soft particles in the site palette, ~2-second duration, no sound.
|
|||
|
|
|
|||
|
|
2. **Should index pages (e.g., `/practices/`) track action items?**
|
|||
|
|
Index pages use grid cards, not task lists. Likely `action_points: false` by default for any page without `- [ ]` items. But if someone adds checklist items to an index, should they track? Probably yes — detection is automatic unless explicitly opted out.
|
|||
|
|
|
|||
|
|
3. **Counter placement on headings — inline badge or superscript?**
|
|||
|
|
`## Meditation [0/2]` (inline, after heading text) vs `## Meditation` with a small pill badge. Inline is simpler, less DOM manipulation, and works with existing heading anchors. I'd recommend inline.
|
|||
|
|
|
|||
|
|
4. **Navigation badges — all pages or only pages with action items?**
|
|||
|
|
Only pages with at least one action item should get a badge. Empty counters clutter the sidebar. This means we compute on first page visit and store the `total` count — pages with total=0 get no badge.
|
|||
|
|
|
|||
|
|
5. **When a user edits the Markdown source (adding/removing items), how should we handle stale localStorage entries?**
|
|||
|
|
Stale entries (label text no longer matches any rendered checkbox) are silently dropped. New items start unchecked. This is the least-surprising behavior — edits "reset" progress only for changed items, not the whole page.
|
|||
|
|
|
|||
|
|
6. **Should completion state be exportable/downloadable?**
|
|||
|
|
A "download my progress" button would let users save their state as a JSON file before clearing browser data. Nice-to-have for v1.1.
|
|||
|
|
|
|||
|
|
7. **Confetti: custom canvas or `canvas-confetti` library?**
|
|||
|
|
- Custom: ~60 lines of JS, full control over palette and behavior, zero dependency, but more code to maintain
|
|||
|
|
- `canvas-confetti`: battle-tested, good defaults, one function call, but adds ~7 KB dependency
|
|||
|
|
|
|||
|
|
Leaning custom — the palette matching and reduced-motion respect are trivial to implement and the site already values self-contained minimalism.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 8. Implementation Plan (high-level)
|
|||
|
|
|
|||
|
|
| Phase | What | Files touched |
|
|||
|
|
|-------|------|--------------|
|
|||
|
|
| 1 | Core checkbox persistence (read/write localStorage) | `docs/javascripts/extra.js` |
|
|||
|
|
| 2 | Heading counters + color change | `docs/javascripts/extra.js`, `docs/stylesheets/extra.css` |
|
|||
|
|
| 3 | Confetti on completion | `docs/javascripts/extra.js` (custom canvas) |
|
|||
|
|
| 4 | Navigation badge injection | `docs/javascripts/extra.js`, `docs/stylesheets/extra.css` |
|
|||
|
|
| 5 | Page-level h1 counter | `docs/javascripts/extra.js`, `docs/stylesheets/extra.css` |
|
|||
|
|
| 6 | Opt-out frontmatter support | `docs/javascripts/extra.js` |
|
|||
|
|
| 7 | Test across all content pages + mobile | manual QA |
|
|||
|
|
|
|||
|
|
All JS goes in the existing `docs/javascripts/extra.js`. All CSS in `docs/stylesheets/extra.css`. No new files, no new dependencies, no build pipeline changes.
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 9. Success Criteria
|
|||
|
|
|
|||
|
|
- [ ] Clicking a `- [ ]` checkbox toggles its visual state
|
|||
|
|
- [ ] Refreshing the page preserves checked state
|
|||
|
|
- [ ] Navigating to another page and back preserves checked state
|
|||
|
|
- [ ] Headings show accurate counters (e.g., `2/5`)
|
|||
|
|
- [ ] When a heading reaches full completion, it changes to accent color
|
|||
|
|
- [ ] Confetti plays when the last item under a heading is checked (and not on reduced-motion)
|
|||
|
|
- [ ] Navigation sidebar shows badges for pages with action items
|
|||
|
|
- [ ] Fully-completed pages show ✓ checkmark in nav
|
|||
|
|
- [ ] Page h1 shows overall completion counter
|
|||
|
|
- [ ] Zero console errors on any page
|
|||
|
|
- [ ] Works on mobile (touch events fire correctly on checkboxes)
|
|||
|
|
- [ ] No visual regression on pages without action items
|