Docs Hub sprints 1+2 closure + sprint 2 build (Hub landing, viewer overhaul)#57
Merged
Merged
Conversation
Adds tests/test_docs_registry_qa.py covering the gaps in the builder's 36 sprint tests, weighted per ARCHITECTURE.md §8 (30% security / 25% regression / 20% happy / 15% setup / 10% Buddy voice). Security (9): Jinja/HTML/JS payloads stored as opaque data; non-string tags coerced; path-traversal/URL/null-byte in related: never opens files; dict-shape related: handled; BOM/CRLF + non-UTF8 + 2 MB doc; concurrent readers under invalidation. Regression (4): nav order Knowledge < Docs < Agents pinned; unknown LAB_TIER doesn't 500; denylist effective despite valid frontmatter; in-place mtime edit invalidates cache (addresses REVIEW W2). Happy (3): new-file pickup, sibling boundaries, empty related: falls back to tag overlap. Setup (3): side-effect-free import, python-frontmatter>=1.1.0 satisfied, portal app imports clean. Buddy voice (1): three populated buddy_prompts checked for "Pip" collision and generic-AI phrasing. 56/56 sprint tests green. No defects found. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…y into Hub + viewer Sprint 2 of the Docs Hub effort. Phase B (Hub landing replacing the /docs redirect) + Phase C (3-column viewer with TOC, siblings, related, Ask Buddy CTA stub) + two Sprint-1 carry-overs (tier filter at render boundary, rename docs/design.md to resolve slug collision). Scope ceiling ~700 LOC across ~6 files. Top failure modes: tier leak (architect doc on min Hub), path-traversal guard preservation, slug- collision regression on the rename. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ug collision) - git mv docs/design.md -> docs/portal-design.md; update frontmatter title - Remove "design.md" from _DOCS_DENYLIST in docs_registry.py (F11 atomic) - Add GET /docs/design.md -> 301 /docs/portal-design.md redirect in app.py (F10) - Import docs_registry in app.py (used in Steps 2-3) - Add test_legacy_design_redirect (F10) and test_no_slug_collision_after_rename (F11) - All 38 tests pass (31 registry + 7 routes) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace RedirectResponse at /docs with docs_hub() handler - Add _filter_by_tier, _featured_docs, _recently_updated helpers in app.py - New templates/docs_hub.html: hero + search filter + featured strip + recently-updated chips + category sections + footer; Jinja autoescape prevents XSS on card titles (F8) - Tests 1-8 from §6.1 all pass (hub 200, min/max tier, empty registry fallback, featured strip, search input, XSS escaping) - Total: 45 tests passing Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… Buddy CTA - Add _slugify() and _render_with_toc() helpers in app.py: extracts H2/H3 headings with stable IDs (deduped with numeric suffix, F6) degrades to toc=[] on any parse error (F5) - Widen serve_local_doc() handler: audience gate (F15): architect docs return upgrade-hint panel on min tier, title not revealed in response registry context: doc, toc, siblings_prev/next, related, buddy_prompt_url (F9: URL-encoded via urllib.parse.quote_plus) - Rewrite doc_viewer.html: 3-column grid (left rail / center article / right TOC) collapses to single column below 900px; preserves .doc-shell styles for center; footer strip: prev/next chips, related cards, Ask Buddy CTA stub (Sprint 3 wires chat-side seed consumption — TODO comment in template) degrades gracefully when doc=None (INDEX.md, no registry entry, F2) - Tests 9-17, 20, 21 from §6.1 all pass - Total: 56 tests passing Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…der docs/ too Closes the LOW-severity S1 finding from docs-hub-sprint-1 QA: CLAUDE.md / AGENTS.md / README.md / CODE_OF_CONDUCT.md were in _ROOT_DENYLIST but not _DOCS_DENYLIST, so dropping any of them into docs/ would register them as user-facing docs. Fix: compose _DOCS_DENYLIST with _ROOT_DENYLIST at module load. One line, visible-at-source. Existing pin test (test_root_denylist_files_in_docs_dir_dont_leak) flipped from "pin current leak" to "assert no leak". Also pins Sprint 1 REVIEW.md + SPRINT.md final state (PASS verdicts). Tests: 78/78 (registry + qa + routes). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… new QA tests QA findings: 0 FAIL, 1 INFO (pre-existing sys.modules pattern in sprint-1 QA file — Sprint 3 carry-over). Product correctness clean in isolation; cross-suite flakes are test-infra only. Coverage: XSS×21 (escape verified, SSTI canary not evaluated), path-traversal×5 (incl. URL-encoded variants), tier title-leak sentinel, concurrent registry/hub reads, 100-doc stress, unicode TOC headings, Buddy URL encoding (5KB + newlines + special chars). Closes docs-hub-sprint-2. Ready to ship bundled with sprint-1 S1 carry-over fix in PR. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two sprints bundled for v0.5 release prep:
docs-hub-sprint-1 — closure
_DOCS_DENYLISTcomposed with_ROOT_DENYLISTat module load so root-denied filenames (CLAUDE.md / AGENTS.md / README.md / CODE_OF_CONDUCT.md) can never register as user-facing docs even if dropped intodocs/.docs-hub-sprint-2 — Phase B + C
src/arail/portal/templates/docs_hub.html— Hub landing replaces the/docs → /docs/INDEX.mdredirect. Hero, featured strip, category sections, client-side search filter, all driven fromdocs_registry.by_category().src/arail/portal/templates/doc_viewer.html— 3-column viewer with siblings (left rail), TOC H2/H3 (right rail), and footer strip (prev/next + related + "Ask Buddy about this" CTA)._filter_by_tier) applied on both hub and viewer —architect-audience docs hidden onmintier; tier-blocked viewer passesdoc=Noneso the title never leaks.docs/design.md → docs/portal-design.md(resolves slug collision with rootdesign.md; atomic with denylist removal — F11).del sys.modulespattern in sprint-1 QA — documented as Sprint 3 carry-over).Test totals
Test plan
pytest tests/test_docs_registry.py tests/test_docs_routes.py tests/test_docs_registry_qa.py tests/test_docs_routes_qa.py→ 125/125 in isolation/docsrenders the new Hub (not the old INDEX.md redirect)/docs/<slug>— siblings, TOC, related, "Ask Buddy" CTA all rendermintier,architect-audience docs are hidden from Hub and return a tier-block page from viewerdocs/portal-design.mdaccessible at/docs/portal-design; rootdesign.mdstill resolves at/docs/designSprint 3 carry-overs
del sys.modulespattern intests/test_docs_registry_qa.py:41(usemonkeypatch.setattrto rebindarail.portal.app._docs_registryinstead).body_html.replace(..., 1)to a token-walking renderer if exotic markdown lands.docs/; full cross-link audit; deletedocs/INDEX.md.🤖 Generated with Claude Code