# 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 `
`. 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:** - `
` 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 . 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" }} ``` - [ ] **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 }}
{{ i18n "docsMenu" }}
{{ partial "docs-tree.html" . }}
{{ if $hasToc }}
{{ i18n "docsOnThisPage" }}
{{ $toc }}
{{ end }}
``` - [ ] **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 }} {{ end }} ``` - [ ] **Step 5: Create `layouts/hardenedpass/single.html`** ```go-html-template {{ define "main" }}
{{ partial "docs-nav.html" . }}
{{ partial "breadcrumb.html" . }}

{{ .Title }}

{{ .Content }}
{{ partial "docs-pager.html" . }}
{{ end }} ``` - [ ] **Step 6: Create `layouts/hardenedpass/list.html`** ```go-html-template {{ define "main" }}
{{ partial "docs-nav.html" . }}
{{ partial "breadcrumb.html" . }}

{{ .Title }}

{{ with .Content }}
{{ . }}
{{ end }}
    {{ range .Pages.ByWeight }}
  • {{ .Title }} {{ with .Description }}

    {{ . }}

    {{ end }}
  • {{ end }}
{{ 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 `
` 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.