summaryrefslogtreecommitdiffstats
path: root/docs/superpowers/plans
diff options
context:
space:
mode:
Diffstat (limited to 'docs/superpowers/plans')
-rw-r--r--docs/superpowers/plans/2026-11-05-hardenedpass-docs.md420
1 files changed, 420 insertions, 0 deletions
diff --git a/docs/superpowers/plans/2026-11-05-hardenedpass-docs.md b/docs/superpowers/plans/2026-11-05-hardenedpass-docs.md
new file mode 100644
index 0000000..b5d408c
--- /dev/null
+++ b/docs/superpowers/plans/2026-11-05-hardenedpass-docs.md
@@ -0,0 +1,420 @@
+# HardenedPass Docs Section Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Add a bilingual, wiki-like HardenedPass documentation section to danix.xyz.
+
+**Architecture:** A new Hugo section `hardenedpass` with its own `list` and `single` layouts in the theme submodule. A sidebar shows the page tree and table of contents; on mobile both collapse into native `<details>`. Pages carry a unique `weight` that gives one reading order across subsections.
+
+**Tech Stack:** Hugo extended, Tailwind 3 (`npm run build`), TOML front matter, Go templates.
+
+**Spec:** `docs/superpowers/specs/2026-11-05-hardenedpass-docs-design.md`
+
+**Deviations from spec, found while planning:**
+- `<details>` cannot be open on desktop and closed on mobile without JavaScript, so the tree renders twice, from a separate `docs-tree.html`. Prev/next is in `docs-pager.html`, because it sits in the content column.
+- The menu entry moves to the last task. A `pageRef` to a draft page has no target, so adding it earlier could break the menu if `master` is deployed before the pages are published.
+
+**Rules for every task:**
+- `SITE` is `~/Programming/GIT/danix.xyz-hacker-theme`, `THEME` is `$SITE/themes/danix-xyz-hacker`, `APP` is `~/Programming/GIT/HardenedPass`.
+- Build into a scratch directory, never `public/`: `hugo -D --gc --minify -d "$OUT"`, with `OUT` a temporary directory.
+- Never touch the `production` branch. Commit GPG signed. Do not commit `.claude/settings.local.json`. Push only when the maintainer asks.
+- All new pages have `draft = true`.
+
+---
+
+### Task 1: Skeleton pages
+
+**Files:**
+- Create: `content/{en,it}/hardenedpass/_index.md`
+- Create: `content/{en,it}/hardenedpass/{getting-started,using,reference}/_index.md`
+- Create: `content/{en,it}/hardenedpass/getting-started/{concepts,install,create-store,connect}/index.md`
+- Create: `content/{en,it}/hardenedpass/using/{entries,otp,sync,autofill}/index.md`
+- Create: `content/{en,it}/hardenedpass/reference/{settings,security,troubleshooting}/index.md`
+
+- [ ] **Step 1: Generate the 30 files**
+
+```bash
+cd "$SITE"
+python3 - <<'EOF'
+import pathlib
+secs = {
+ "getting-started": (10, "Getting started", "Per iniziare", [
+ ("concepts", 10, "Concepts", "Concetti"), ("install", 20, "Install", "Installazione"),
+ ("create-store", 30, "Create a store", "Crea un archivio"), ("connect", 40, "Connect the app", "Collega l'app")]),
+ "using": (20, "Using the app", "Usare l'app", [
+ ("entries", 50, "Entries", "Voci"), ("otp", 60, "OTP codes", "Codici OTP"),
+ ("sync", 70, "Sync", "Sincronizzazione"), ("autofill", 80, "Autofill", "Autofill")]),
+ "reference": (30, "Reference", "Riferimento", [
+ ("settings", 90, "Settings", "Impostazioni"), ("security", 100, "Security", "Sicurezza"),
+ ("troubleshooting", 110, "Troubleshooting", "Risoluzione dei problemi")]),
+}
+def put(path, title, weight=None):
+ w = f"weight = {weight}\n" if weight else ""
+ path.parent.mkdir(parents=True, exist_ok=True)
+ path.write_text(f'+++\ntitle = "{title}"\ndate = 2026-11-05T00:00:00Z\ndescription = ""\n{w}draft = true\n+++\n\nPlaceholder.\n')
+for i, lang in enumerate(("en", "it")):
+ root = pathlib.Path("content") / lang / "hardenedpass"
+ put(root / "_index.md", "HardenedPass")
+ for slug, (w, en, it, pages) in secs.items():
+ put(root / slug / "_index.md", (en, it)[i], w)
+ for p, pw, pen, pit in pages:
+ put(root / slug / p / "index.md", (pen, pit)[i], pw)
+EOF
+find content -path '*hardenedpass*' -name '*.md' | wc -l
+```
+Expected: `30`
+
+No commit yet: Task 2 commits these with the layouts that render them.
+
+---
+
+### Task 2: Layouts
+
+**Files (all in `THEME`):**
+- Create: `layouts/hardenedpass/single.html`, `layouts/hardenedpass/list.html`
+- Create: `layouts/partials/docs-nav.html`, `layouts/partials/docs-tree.html`, `layouts/partials/docs-pager.html`
+- Modify: `layouts/index.json:2-3`
+
+**Files (in `SITE`):**
+- Modify: `i18n/en.yaml`, `i18n/it.yaml` (append)
+
+- [ ] **Step 1: Write the failing check**
+
+```bash
+cat > "$OUT_CHECK" <<'EOF'
+#!/bin/bash
+# Usage: check.sh <build dir>. Fails if the docs section is not wired up.
+set -e
+d=$1
+c=$d/en/hardenedpass/getting-started/concepts/index.html
+[ -f "$c" ] || c=$d/hardenedpass/getting-started/concepts/index.html
+k=${c/concepts/connect}
+u=${c/getting-started\/concepts/using\/entries}
+grep -q 'rel=.\?next' "$c"
+grep -Eq 'href="?[^" >]*/install/"?[^>]*rel=.?next' "$c"
+grep -Eq 'href="?[^" >]*/using/entries/"?[^>]*rel=.?next' "$k"
+grep -Eq 'href="?[^" >]*/connect/"?[^>]*rel=.?prev' "$u"
+grep -q 'aria-current' "$c"
+[ "$(grep -o 'id="\?TableOfContents' "$c" | wc -l)" = 0 ]
+echo PASS
+EOF
+```
+`OUT_CHECK` is a scratch path outside the repo. Run it on a build: `hugo -D --gc --minify -d "$OUT" && bash "$OUT_CHECK" "$OUT"`.
+Expected: exits non-zero, because the default layout has no `rel=next`.
+
+- [ ] **Step 2: Create `layouts/partials/docs-tree.html`**
+
+```go-html-template
+{{ $current := . }}
+{{ $root := site.GetPage "/hardenedpass" }}
+<ul class="space-y-4 text-sm">
+ <li><a href="{{ $root.RelPermalink }}" class="font-mono text-accent">{{ $root.Title }}</a></li>
+ {{ range $root.Sections.ByWeight }}
+ <li>
+ <a href="{{ .RelPermalink }}" class="font-mono text-accent">{{ .Title }}</a>
+ <ul class="mt-2 space-y-1 border-l border-border pl-3">
+ {{ range .RegularPages.ByWeight }}
+ <li>
+ {{ if $current.Eq . }}
+ <a href="{{ .RelPermalink }}" aria-current="page" class="text-accent font-bold">{{ .Title }}</a>
+ {{ else }}
+ <a href="{{ .RelPermalink }}" class="text-text-dim hover:text-accent">{{ .Title }}</a>
+ {{ end }}
+ </li>
+ {{ end }}
+ </ul>
+ </li>
+ {{ end }}
+</ul>
+```
+
+- [ ] **Step 3: Create `layouts/partials/docs-nav.html`**
+
+The tree and table of contents render twice, so the `id` Hugo puts on the table of contents is removed to keep ids unique.
+
+```go-html-template
+{{ $toc := replace .TableOfContents ` id="TableOfContents"` "" | safeHTML }}
+{{ $hasToc := gt (len .Fragments.Headings) 0 }}
+<div class="lg:hidden mb-8 space-y-2">
+ <details class="border border-border p-3">
+ <summary class="font-mono cursor-pointer">{{ i18n "docsMenu" }}</summary>
+ <div class="mt-3">{{ partial "docs-tree.html" . }}</div>
+ </details>
+ {{ if $hasToc }}
+ <details class="border border-border p-3">
+ <summary class="font-mono cursor-pointer">{{ i18n "docsOnThisPage" }}</summary>
+ <div class="mt-3 text-sm text-text-dim">{{ $toc }}</div>
+ </details>
+ {{ end }}
+</div>
+<aside class="hidden lg:block lg:sticky lg:top-24 lg:self-start lg:max-h-[calc(100vh-7rem)] lg:overflow-y-auto"
+ aria-label="{{ i18n "docsMenu" }}">
+ {{ partial "docs-tree.html" . }}
+ {{ if $hasToc }}
+ <div class="mt-8 border-t border-border pt-4 text-sm text-text-dim">
+ <p class="font-mono text-accent mb-2">{{ i18n "docsOnThisPage" }}</p>
+ {{ $toc }}
+ </div>
+ {{ end }}
+</aside>
+```
+
+- [ ] **Step 4: Create `layouts/partials/docs-pager.html`**
+
+`Pages.Next` steps toward index 0, so the list is reversed to make "next" the following page in reading order.
+
+```go-html-template
+{{ $pages := (site.GetPage "/hardenedpass").RegularPagesRecursive.ByWeight.Reverse }}
+{{ $prev := $pages.Prev . }}
+{{ $next := $pages.Next . }}
+{{ if or $prev $next }}
+<nav class="flex justify-between gap-4 border-t border-border pt-6 font-mono text-sm" aria-label="{{ i18n "docsPager" }}">
+ <span>{{ with $prev }}<a href="{{ .RelPermalink }}" rel="prev" class="text-accent">◄ {{ .Title }}</a>{{ end }}</span>
+ <span class="text-right">{{ with $next }}<a href="{{ .RelPermalink }}" rel="next" class="text-accent">{{ .Title }} ►</a>{{ end }}</span>
+</nav>
+{{ end }}
+```
+
+- [ ] **Step 5: Create `layouts/hardenedpass/single.html`**
+
+```go-html-template
+{{ define "main" }}
+<article class="mx-auto px-4 py-12 max-w-7xl">
+ <div class="content-grid lg:grid lg:grid-cols-[16rem_minmax(0,1fr)] lg:gap-10">
+ {{ partial "docs-nav.html" . }}
+ <div class="min-w-0">
+ {{ partial "breadcrumb.html" . }}
+ <h1 class="text-4xl md:text-5xl font-bold mb-8 text-accent font-oxanium">{{ .Title }}</h1>
+ <div class="prose dark:prose-invert max-w-none mb-12">
+ {{ .Content }}
+ </div>
+ {{ partial "docs-pager.html" . }}
+ </div>
+ </div>
+</article>
+{{ end }}
+```
+
+- [ ] **Step 6: Create `layouts/hardenedpass/list.html`**
+
+```go-html-template
+{{ define "main" }}
+<section class="mx-auto px-4 py-12 max-w-7xl">
+ <div class="content-grid lg:grid lg:grid-cols-[16rem_minmax(0,1fr)] lg:gap-10">
+ {{ partial "docs-nav.html" . }}
+ <div class="min-w-0">
+ {{ partial "breadcrumb.html" . }}
+ <h1 class="text-4xl md:text-5xl font-bold mb-8 text-accent font-oxanium">{{ .Title }}</h1>
+ {{ with .Content }}
+ <div class="prose dark:prose-invert max-w-none mb-12">{{ . }}</div>
+ {{ end }}
+ <ul class="space-y-6">
+ {{ range .Pages.ByWeight }}
+ <li>
+ <a href="{{ .RelPermalink }}" class="text-xl text-accent font-oxanium">{{ .Title }}</a>
+ {{ with .Description }}<p class="text-text-dim">{{ . }}</p>{{ end }}
+ </li>
+ {{ end }}
+ </ul>
+ </div>
+ </div>
+</section>
+{{ end }}
+```
+
+- [ ] **Step 7: Add the section to `layouts/index.json`**
+
+Replace lines 2-3 with:
+
+```go-html-template
+{{- $projects := where .Site.RegularPages "Section" "slackware" -}}
+{{- $docs := where .Site.RegularPages "Section" "hardenedpass" -}}
+{{- $articles = ($articles | append $projects | append $docs).ByDate.Reverse -}}
+```
+
+- [ ] **Step 8: Add i18n keys**
+
+```bash
+cd "$SITE"
+cat >> i18n/en.yaml <<'EOF'
+docsMenu: "Documentation"
+docsOnThisPage: "On this page"
+docsPager: "Previous and next page"
+EOF
+cat >> i18n/it.yaml <<'EOF'
+docsMenu: "Documentazione"
+docsOnThisPage: "In questa pagina"
+docsPager: "Pagina precedente e successiva"
+EOF
+```
+
+- [ ] **Step 9: Rebuild CSS and run the check**
+
+```bash
+cd "$SITE" && npm run build && hugo -D --gc --minify -d "$OUT" && bash "$OUT_CHECK" "$OUT"
+```
+Expected: `PASS`. If the `rel=next` lines fail because prev and next are swapped, the installed Hugo differs from what Step 4 assumes: drop `.Reverse`.
+
+- [ ] **Step 10: Check by eye**
+
+Run `hugo server -D` and open `/hardenedpass/getting-started/concepts/` and `/it/hardenedpass/getting-started/concepts/` at 1280px and 390px width. Expected: sticky left column on desktop; two closed `<details>` on mobile; sticky column clears the site header. Fix `lg:top-24` if the header covers it.
+
+- [ ] **Step 11: Commit theme, then content**
+
+```bash
+cd "$THEME"
+git add layouts/hardenedpass layouts/partials/docs-nav.html layouts/partials/docs-tree.html layouts/partials/docs-pager.html layouts/index.json assets/css/main.min.css
+git commit -m "feat: add layouts for the HardenedPass docs section"
+cd "$SITE"
+git add content/en/hardenedpass content/it/hardenedpass i18n themes/danix-xyz-hacker docs/superpowers/plans/2026-11-05-hardenedpass-docs.md
+git commit -m "content: add HardenedPass docs skeleton as drafts"
+```
+Each message ends with the `Co-Authored-By` line from the session. **Checkpoint: maintainer reviews the layout.**
+
+---
+
+### Task 3: Emulator for screenshots
+
+- [ ] **Step 1: Start the environment**
+
+```bash
+cd "$APP"
+export ANDROID_SERIAL=emulator-5554
+scripts/e2e-env.sh up
+"$ANDROID_HOME/emulator/emulator" -avd aps-test -no-audio -no-boot-anim -no-snapshot-save -gpu swiftshader_indirect &
+./gradlew :app:installDebug
+```
+Windowed: no `-no-window`. Needs the host's `libbsd`; if the emulator fails on it, stop and tell the maintainer. Follow the emulator notes in `$APP/AGENTS.md`: wait for boot and keyguard, disable animations, `settings put secure autofill_service null`, `scripts/e2e-env.sh push-keys`. The app is cloned from `ssh://git@10.0.2.2:2222/srv/git/store.git`. Unlike a real device, the file picker here shows only throwaway files, so the agent drives it.
+
+- [ ] **Step 2: Confirm secure screens capture**
+
+Open entry `example.org` and ask the maintainer to screenshot the emulator window. If the capture is black, secure screens stay text only and the shot lists below drop them.
+
+- [ ] **Step 3: Set language per pass**
+
+Capture everything in English first, then `adb shell "cmd locale set-app-locales xyz.danix.hardenedpass.debug --locales it"` and capture again.
+
+---
+
+### Task 4: getting-started pages
+
+**Files:** `content/{en,it}/hardenedpass/getting-started/_index.md`, and `{concepts,install,create-store,connect}/index.md`.
+
+For each page: read the sources listed, verify each step, write EN and IT, fill `description` so that the first 160 characters name the topic, and use `{{< ref >}}` for internal links. Plain second person, short steps. Images are page bundle files or `/uppies/2026/11/`, through the `image` shortcode with `alt` text. End each page with no recap: the pager links onward.
+
+- [ ] **Step 1: `concepts`.** Password as an encrypted file, folder as a group, the first line is the password and later lines are metadata (`username:`, `url:`, `otpauth://`). OpenPGP public and private key and passphrase. Git as sync and history. How the three fit: `.gpg-id` names the recipients. Sources: `$APP/format/common/`, `https://www.passwordstore.org/`. Link out for depth.
+
+- [ ] **Step 2: `install`.** From GitHub Releases: `HardenedPass_vX.Y.Z.apk` and `.sha256`. `sha256sum -c` on desktop. Certificate SHA-256 from `$APP/AGENTS.md` under Releases, checked with `apksigner verify --print-certs`. Android's unknown sources prompt. Installs next to upstream, which is not migrated. Update by installing the newer APK; the same key is required. Check the exact release asset names against `gh release view v0.2.0 --repo danixland/HardenedPass`.
+
+- [ ] **Step 3: `create-store`.** On a desktop: `gpg --full-generate-key`, `pass init`, `pass git init`, private remote on any host, `pass git push`. Exporting the private key for the phone with `gpg --armor --export-secret-keys` and handling that file safely. Run every command in a temporary `GNUPGHOME` and `PASSWORD_STORE_DIR`, never the real ones. Check in `$APP` whether the app can generate a PGP key or create a store with no desktop, and document the local repository path that the welcome screen offers.
+
+- [ ] **Step 4: `connect`.** Welcome screen, clone, URL forms, SSH key import or generation, password auth, host key prompt, PGP import screen, then the store list. Source: the setup screens in `$APP/app/src/main/java/app/passwordstore/ui/`, and the URL warnings in `strings.xml` (`ssh_scheme_needed_title`, `https_scheme_with_port_title`, `git_scheme_disallowed_title`).
+
+- [ ] **Step 5: Screenshots.** Ask the maintainer for: welcome screen, repository info, SSH key screen, PGP import with recipients, store list. Both languages.
+
+- [ ] **Step 6: Build, check and commit**
+
+```bash
+cd "$SITE" && hugo -D --gc --minify -d "$OUT" && bash "$OUT_CHECK" "$OUT"
+git add content/en/hardenedpass content/it/hardenedpass static/uppies/2026/11
+git commit -m "content: write HardenedPass getting-started pages"
+```
+Expected: `PASS`, no `REF_NOT_FOUND`. **Checkpoint: maintainer reviews, including the Italian.**
+
+---
+
+### Task 5: using pages
+
+**Files:** `content/{en,it}/hardenedpass/using/_index.md`, and `{entries,otp,sync,autofill}/index.md`. Same method as Task 4.
+
+- [ ] **Step 1: `entries`.** Browsing, search and recursive filtering, opening an entry, copy and clipboard timeout, create, edit, move, delete, the random and diceware generators. Verify each on the emulator.
+
+- [ ] **Step 2: `otp`.** Storing an `otpauth://` line, the live code on the entry screen. The e2e store's `otp/example.com` shows it. The OTP screen never goes idle, so check it by eye.
+
+- [ ] **Step 3: `sync`.** Pull to sync, push, automatic commit on change, rebase on pull, conflict recovery (abort rebase and push a new branch, hard reset). Sources: `$APP/app/src/main/java/app/passwordstore/util/git/`, `BreakOutOfDetached`, `StoreActions`.
+
+- [ ] **Step 4: `autofill`.** Enabling the service, fill by chip or dropdown, the search chip, save flow, the phishing warning from `oreo_autofill_warning_publisher_dataset_title`, supported browsers from `$APP/autofill-parser/`. The emulator cannot do autofill (see `$APP/AGENTS.md`); write from the code and the 2026-11-05 tablet and Pixel runs. Firefox shows its own save prompt too: say so.
+
+- [ ] **Step 5: Screenshots.** Ask for: entry view with test data, editor, generator sheet, OTP code, conflict dialog if reachable. Autofill shots, if wanted, come from a real device with the test page and test store only: ask the maintainer.
+
+- [ ] **Step 6: Build, check and commit**
+
+```bash
+cd "$SITE" && hugo -D --gc --minify -d "$OUT" && bash "$OUT_CHECK" "$OUT"
+git add content/en/hardenedpass content/it/hardenedpass static/uppies/2026/11
+git commit -m "content: write HardenedPass using pages"
+```
+**Checkpoint: maintainer review.**
+
+---
+
+### Task 6: reference and landing pages
+
+**Files:** `content/{en,it}/hardenedpass/reference/_index.md`, `{settings,security,troubleshooting}/index.md`, `content/{en,it}/hardenedpass/_index.md`, `static/uppies/2026/11/hardenedpass-banner.png` (from `$APP/media/banner.png`).
+
+- [ ] **Step 1: `settings`.** Every setting, grouped as the Settings screen groups them. Start from `pref_*_title` in `$APP/app/src/main/res/values/strings.xml` and `values-it/strings.xml`, and confirm each is still shown in the Settings code: use the app's own Italian labels.
+
+- [ ] **Step 2: `security`.** Secrets are AES-GCM encrypted under Android Keystore keys, the PGP passphrase cache and its options, biometric unlock, `FLAG_SECURE`, no telemetry, no Play Services, the exported activity check on file paths. What it does not protect from: compromised unlocked device, a malicious keyboard, clipboard readers before the timeout, git host metadata (entry names are visible as file names). Sources: `$APP/AGENTS.md`, `KeystorePreferences.kt`. Every claim must be traceable to code.
+
+- [ ] **Step 3: `troubleshooting`.** Real errors from `strings.xml` with their cause and fix: wrong passphrase, no matching key, key without encryption subkey, host key changed, authentication failure, push rejected, detached HEAD. Source each one in the code path that raises it.
+
+- [ ] **Step 4: Landing page.** First person, site tone: why the fork exists, GrapheneOS goal, differences from upstream from `$APP/README.md`, credit to the Android Password Store authors, links to `install`, `connect`, GitHub and the issue tracker. Banner through the `image` shortcode. "GrapheneOS" appears as a goal only, never as part of a product name.
+
+- [ ] **Step 5: Build, check and commit**
+
+```bash
+cd "$SITE" && hugo -D --gc --minify -d "$OUT" && bash "$OUT_CHECK" "$OUT"
+git add content/en/hardenedpass content/it/hardenedpass static/uppies/2026/11
+git commit -m "content: write HardenedPass reference and landing pages"
+```
+**Checkpoint: maintainer review.**
+
+---
+
+### Task 7: Clean up and publish prep
+
+- [ ] **Step 1: Stop the environment**
+
+```bash
+cd "$APP"; adb emu kill; scripts/e2e-env.sh down
+```
+
+- [ ] **Step 2: With the maintainer's approval only, flip drafts**
+
+```bash
+cd "$SITE"
+grep -rl '^draft = true' content/en/hardenedpass content/it/hardenedpass | xargs sed -i 's/^draft = true/draft = false/'
+```
+
+- [ ] **Step 3: Add the menu entry**
+
+In `hugo.toml`, for both `languages.it` and `languages.en`, change `legal` to `weight = 7` and add before it:
+
+```toml
+ [[languages.en.menus.main]]
+ identifier = "hardenedpass"
+ pageRef = "/hardenedpass"
+ name = "hardenedpass"
+ weight = 6
+```
+Use `languages.it.menus.main` in the Italian block.
+
+- [ ] **Step 4: Final build without `-D`**
+
+```bash
+cd "$SITE" && hugo --gc --minify -d "$OUT" && bash "$OUT_CHECK" "$OUT"
+grep -c hardenedpass "$OUT/en/index.json" "$OUT/it/index.json" 2>/dev/null || grep -rl 'hardenedpass' "$OUT" --include=index.json
+```
+Expected: `PASS`, and the search index lists the pages.
+
+- [ ] **Step 5: Commit, then tell the maintainer**
+
+```bash
+git add hugo.toml content/en/hardenedpass content/it/hardenedpass
+git commit -m "content: publish the HardenedPass docs section"
+```
+The maintainer pushes and merges `master` into `production`.
+
+- [ ] **Step 6: After the section is live, in `APP`**
+
+Add a `## Documentation` section to `README.md` linking `https://danix.xyz/hardenedpass/`, remove the Website item and the empty Documentation heading from `TODO.md`, and commit.