Tutorial: 2026-07-02

2026-07-02

Newest entry on top.

Tablet tier — split-screen layouts with compact chrome (feature/responsive-foundation, 2.41.0)

Owner reported tablets (820/853/912/1024) fell into desktop mode and looked whacky — header controls ran off-screen, Generate button clipped. Confirmed via 820/1024 screenshots. Decided (via AskUserQuestion) on a tablet tier of 769–1024px with the header collapsing behind the ⋯ menu.

The key idea: split the responsive system into two breakpoints instead of one.

  • Compact chrome → <=1024px: widened useCompact() (768→1024, so Insert-menu / inline-Links / Size-cog switch on for tablets) and moved the header-overflow (topbar-responsive.css), composer field-bar wrap (composer-field.css), Prompt/Negative height match (compose-toolbar.css), and submenu bottom-sheets (mobile-sheets.css) to <=1024px.
  • Layout-collapse stays <=768px (CSS-only, phones): the Generate palette drawer and Manage master/detail are untouched, so tablets keep the two-pane split. Split the topbar block so the wordmark-hide + tab-tighten stay phone-only (tablets keep them).

Verified desktop resumes at >=1025 (overflow toggle hidden at 1025 + 1280; desktop header inline unchanged). Added 3 tablet E2E specs. Gate green (check:docs, lint, smoke, 260 unit + 313 SPA + 21 E2E). MINOR bump — it's a new responsive tier / milestone.

Mobile links fold into the overflow menu + app version on show (feature/responsive-foundation, 2.40.23)

Two user-requested header/footer items during the live UI review:

  • Links inline on mobile. The phone overflow ("⋯") menu's "Links" row opened another separate bottom sheet — a nested, differently-shaped surface the owner (rightly) called dumb. LinksMenu now has a compact branch (via a new shared useCompact() hook, extracted from DplInsertBar) that folds the project links → legal pages → language picker directly into the overflow menu as inline rows — one panel, no nested trigger/sheet. Desktop keeps the icon-button dropdown byte-for-byte. Dropped the dead mobile .links-pop / .links-scrim sheet rules.
  • App version shown. Baked the canonical version in at build time (gui/vite.config.js define: __APP_VERSION__ from repo-root package.json; gui/src/lib/version.js guards it to "dev" under Vitest) and displayed it as v<VERSION> — faint at the right of the desktop footer, and at the foot of the mobile inline links menu (footer is hidden on phones).

Verified with desktop + Pixel-7 screenshots (self-review). Fixed two tests the changes surfaced: the phone top-bar E2E (assert inline links, not the old .links-trigger) and a stale PromptResult clear-button test (match the icon button's descriptive aria-label). Gate green: lint + 260 unit + 313 SPA + 18 E2E. Also committed the earlier Manage editor polish (2.40.22 — compact name/Save, chevron Back button).

Repo-hygiene guardrails so the drift/litter/uncommitted-file problems can't recur (feature/repo-hygiene)

After the flatten + the multi-round cleanup, added mechanical enforcement so the same problems don't silently accumulate again:

  • Doc-drift guardscripts/check-links.mjs (npm run check:docs) fails on any broken relative Markdown link, so a doc that still points at a renamed/removed file fails the build. Wired into npm test and the CI check job (new step). Catches exactly the kind of web-app.md / expansion.js / listFiles.js broken links this session had to hand-fix.
  • Uncommitted-file guardscripts/check-tidy.mjs (npm run check:tidy) fails on untracked non-ignored files. This is the guard that would have caught the 4 fairyfox reports prior sessions left uncommitted. Run before finishing (not in CI — a fresh checkout has none).
  • Branch-litter fix — found the root cause: the GitHub delete_branch_on_merge repo setting was false, so merged PR branches never auto-deleted. Set it to true.
  • Codified the rules — new notes/reference/repo-hygiene.md runbook + standing instructions in CLAUDE.md (commit everything incl. fairyfox reports; sweep docs in the same change on any rename/remove; delete spent branches; the new checks in the verify loop) + indexed it in the notes.

Tooling/CI/docs only — no version bump. Verified the checks work (check:docs = 314 links OK; check:tidy correctly flagged the two new script files as untracked before they were committed).

Follow-up: enabling delete_branch_on_merge surfaced a subtlety — GitHub deletes a merged PR's head branch, and a dev → main PR's head is dev itself (it got auto-deleted once, recreated by the back-merge). Fixed it with a deletion-only branch protection on dev (blocks deletion + force-push; no PR/review/status requirement, so direct pushes are unchanged). Documented the interaction in repo-hygiene.md. Also wrote a comprehensive fairyfox roundup report (fairyfox-reports/2026-07-02-shareable-systems-roundup.md) proposing the repo-hygiene guardrails, the monorepo-flatten methodology, the docs current-vs-historical discipline, and the honest friction/lessons as candidate hub standards.

Flattened the repo: removed engine-v1-2, engine-v3 → repo root, consolidated stages (2.40.0)

Undid the engine-vN split and cleaned up the tree. Done on feature/flatten-monorepo, in verified checkpoints:

  • Removed engine-v1-2/ — the frozen 2022–2023 CommonJS snapshot (335 files, ~257k lines). It's recoverable from git history and the reference clone at assets/references/og-pre-revival-2023-04-07-241a148/. This also clears its 10 known dependency vulns (the last thing keeping Scorecard's Vulnerabilities check from staying clean). Dropped its entries from the CodeQL + CodeRabbit configs.
  • Flattened engine-v3/ to the repo rootgit mv'd src/, data/, gui/, scripts/, tests/ and the tooling configs up one level (908 renames, history preserved). Rewrote every hardcoded engine-v3/ path: all 6 CI workflows (dropped working-directory: engine-v3, fixed cache/coverage/artifact paths), netlify.toml, jsdoc.config.json, sonar-project.properties, codecov.yml, .coderabbit.yaml, .github/dependabot.yml, .gitignore (de-duped), build-docs.mjs (root === repoRoot now), and manageFs.js's RAW_BASE (main now carries the flat layout).
  • Consolidated live pipeline stagescleanup.js + prompt-salt.js were active stages sitting in a folder labeled "legacy" (src/prompt-modules/); moved them into src/core/stages/ with the other stages and removed the empty folder. Dropped the dead promptModuleFiles setting (0 usages, pointed at the removed folder).
  • Docs — rewrote CLAUDE.md's framing, README's project-layout section, status.md, the systems/* and context/* banners, reference/deployment.md, and the plans that referenced engine-v3/…; kept the dated session/changelog history intact and annotated plans/engine-split.md as reversed. Legal pages unaffected (no data-practice change).

Verified green at each checkpoint: npm run smoke, npm --prefix gui run build (Vite glob over data/blocks resolves), npm run lint, format:check, the Node suite (260) and the SPA suite (313).

Follow-up cleanup (same day, feature/post-flatten-cleanup): committed the 4 pending fairyfox reports (their own commit); rewrote CLAUDE.md's stale src/chdir.js / src/common.js "Critical" bullet (those files + process.chdir don't exist — that was the removed classic system) to the real path model (module-relative loaders via import.meta.url; cwd-relative list/preset settings that rely on npm running from the repo root); and swept the stale web-app/ folder name (renamed to gui/ at 2.7.1) out of the current-state docs, including three broken web-app.mdgui.md links. Investigated the three data/process-*.js data-prep scripts — not broken: they double-chdir (import.meta.dirname then ..) to pin cwd to the repo root, so ./data/sources/*.csv resolves correctly. Left in place (they're build tooling co-located with the data/sources/ CSVs they consume; moving destructive-if-run, untestable scripts wasn't worth the risk).

Scorecard follow-up — verified the real breakdown, fixed Signed-Releases + Branch-Protection badge

After 2.39.0 shipped, the badge moved 4.2 → 6.2. The public Scorecard JSON API lagged (still served the old 4.2 scan), so instead of guessing I downloaded the scorecard binary (v5.5.0) and ran it against the live repo with an admin token for the authoritative per-check breakdown. Findings:

  • Token-Permissions, Pinned-Dependencies, Security-Policy → 10, and CI-Tests → 10 (unlocked by the PR flow). These drove 4.2 → 6.2.
  • Vulnerabilities still 0 — engine-v3 (0) and gui (0) are clean; the frozen engine-v1-2/ has 10 vulns and Scorecard scans the whole repo. Owner is removing engine-v1-2 soon, so left as-is.
  • Signed-Releases still 0 — I had wrongly said it was satisfied. attest-build-provenance writes to the attestations API, which Scorecard's check does not read; it scans release assets for signature files. Fix: added keyless cosign sign-blob to release.yml, attaching .sig + .pem as release assets (kept the attestation too). Activates on the next release.
  • Branch-Protection is really 4/10, not 0 (deletion/force-push blocked, admins enforced, PR + status checks required; the missing points all need review approvals = solo wall). The CI badge reads 0 because the default token can't read protection. Fix: scorecard.yml now passes repo_token: ${{ secrets.SCORECARD_TOKEN || github.token }} (safe fallback). Owner must create a repo-admin-read PAT and save it as SCORECARD_TOKEN to unlock the 4 on the badge.

Both fixes are CI-only (no version bump). release.yml + scorecard.yml re-validated (YAML parses); deployment.md corrected. Signing activates on the next release; the branch-protection badge improves once the PAT secret exists and the next Scorecard run uses it.

Hardened the supply chain — raised the OpenSSF Scorecard from 4.2 (2.39.0)

Owner flagged the README Scorecard badge (4.2) as feeling low. Pulled the live scan from the OpenSSF API and worked the actual failing checks rather than touching the badge. Score was dragged down by, in weight order: Code-Review, Token-Permissions, Pinned-Dependencies, Branch-Protection, Vulnerabilities, Signed-Releases (all high-weight, 0/10), plus Security-Policy, Fuzzing, CII, Contributors.

Executed (owner approved all tiers, incl. moving main to a PR-based flow):

  • Token-Permissions — top-level permissions: contents: read added to ci.yml, codeql.yml, sonar.yml, visual-baselines.yml; release.yml's write dropped to the release job scope (+ id-token/attestations: write there for signing).
  • Pinned-Dependencies — pinned all 33 Action refs to full commit SHAs (# vX comments) via a PowerShell literal-replace pass across the 9 workflow files; verified 0 refs left on a moving tag. Dependabot already covers the github-actions ecosystem, so the SHAs stay fresh.
  • Signed-Releases — added actions/attest-build-provenance@<sha> to release.yml (keyless Sigstore SLSA provenance for the tarball + docs zip). Verify: gh attestation verify <file> --repo ….
  • Branch-Protection — enabled on main via gh api PUT …/branches/main/protection: require PR (0 approvals), strict status checks (the two core CI jobs), enforce-admins, block force-push/deletion, require conversation resolution, linear history OFF (so release merge commits are allowed). Read back and confirmed. dev left unprotected.
  • Security-Policy — added root SECURITY.md (private reporting → fairy@fairyfox.io).
  • Vulnerabilitiesnpm audit --omit=dev was already 0; the 18 OSV hits were all dev-only from @lhci/cli (tmp/inquirer/external-editor/uuid). Added overrides tmp@^0.2.7 + uuid@^11.1.1npm installnpm audit now 0. Confirmed @lhci/cli (0.15.1), uuid.v4, and tmp still load under the forced majors.
  • Code-Review — left at 0 on purpose: it needs an approved PR review and GitHub blocks self-approval, so it's unreachable solo. This caps a one-person repo at ~8.

Docs / standards updated in the same change

  • CLAUDE.md release step (5) rewritten to the PR-based flow (gh pr creategh pr checks --watchgh pr merge --merge → ff dev up to main).
  • notes/reference/git-workflow.md — new "main is branch-protected" section; PATCH / MINOR-MAJOR / hotfix release commands rewritten to PR merges; branch-model + merging notes updated.
  • notes/reference/deployment.md — new "Supply-chain hardening" subsection; Scorecard, release, and provenance notes updated.
  • Version bumped 2.38.1 → 2.39.0 (MINOR, feature set); changelog entry added.

Landmine hit

gh api --method PUT … --input - (stdin pipe) failed with "Problems parsing JSON" — PowerShell pipes strings to a native process's stdin as UTF-16, which gh can't parse. Fix: write the JSON to a UTF-8 (no BOM) temp file and pass --input <file>. Logged in reference/fix-patterns.md.

Follow-ups

  • Wrote a hub proposal (notes/fairyfox-reports/2026-07-02-propose-scorecard-hardening.md) to standardize this hardening across all fairyfox projects — for the owner to carry to the hub (anti-recursion: this session doesn't touch the hub repo).
  • The badge won't move until scorecard.yml re-runs (weekly Monday cron, or the next main push).
  • Signed-Releases only goes green after the next actual release runs the workflow.