# 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
