diff options
| author | Danilo M. <danix@danix.xyz> | 2026-11-05 10:09:23 +0100 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-11-05 10:09:23 +0100 |
| commit | 1da0025fc5d4fa001124a5cf225a69a49a7d01bf (patch) | |
| tree | 426a91e70da28e6f0c5ba7a61ca87ff022e6a0ab /docs/superpowers | |
| parent | 2a6f6f8764657e44c88d5688947d099c1edc83a2 (diff) | |
| download | danixxyz-1da0025fc5d4fa001124a5cf225a69a49a7d01bf.tar.gz danixxyz-1da0025fc5d4fa001124a5cf225a69a49a7d01bf.zip | |
content: add HardenedPass docs skeleton as drafts
Placeholder pages for the new section in both languages, its i18n keys,
the implementation plan and the theme with the section's layouts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'docs/superpowers')
| -rw-r--r-- | docs/superpowers/plans/2026-11-05-hardenedpass-docs.md | 420 |
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. |
