fix(mobile): implement navigation toggle for document viewer

Add mobile-specific navigation pattern to resolve catch-22 UX issue where
users couldn't see documents without scrolling but didn't know to scroll.

Changes:
- Add mobile CSS to toggle between sidebar and document viewer
- Add back button to return to document list on mobile
- Add document-active body class to manage navigation state
- Update GitHub repository links to correct URL

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
TheFlow 2025-10-19 12:41:48 +13:00
parent 79a280a403
commit d8e5061873
2 changed files with 137 additions and 31 deletions

View file

@ -3,8 +3,11 @@
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<title>Framework Documentation | Tractatus AI Safety</title>
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<link rel="icon" type="image/svg+xml" href="/favicon-new.svg">
<!-- PWA Manifest -->
<link rel="manifest" href="/manifest.json">
@ -14,9 +17,11 @@
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<meta name="apple-mobile-web-app-title" content="Tractatus">
<link rel="apple-touch-icon" href="/images/tractatus-icon.svg">
<link rel="apple-touch-icon" href="/images/tractatus-icon-new.svg">
<link rel="stylesheet" href="/css/fonts.css">
<link rel="stylesheet" href="/css/tailwind.css?v=0.1.0.1760254958072">
<link rel="stylesheet" href="/css/tractatus-theme.min.css">
<style>
html { scroll-behavior: smooth; }
@ -430,6 +435,38 @@
#search-modal-content::-webkit-scrollbar-thumb:hover {
background: #94a3b8;
}
/* Mobile document viewer navigation */
@media (max-width: 1023px) {
/* Initially show sidebar, hide main content */
aside {
display: block;
}
main#document-viewer-main {
display: none;
}
/* When document is active, hide sidebar and show content */
body.document-active aside {
display: none;
}
body.document-active main#document-viewer-main {
display: block;
}
}
/* Back button styling (mobile only) */
#back-to-docs-btn {
display: none;
}
@media (max-width: 1023px) {
#back-to-docs-btn {
display: flex;
}
}
</style>
</head>
<body class="bg-gray-50">
@ -539,7 +576,7 @@
GitHub
</h3>
<div class="space-y-2">
<a href="https://github.com/AgenticGovernance/tractatus-framework"
<a href="https://github.com/AgenticGovernance/tractatus"
target="_blank"
rel="noopener noreferrer"
class="flex items-center gap-2 p-2 rounded-lg hover:bg-gray-50 transition group">
@ -551,7 +588,7 @@
<div class="text-xs text-gray-500">Source code, examples & contributions</div>
</div>
</a>
<a href="https://github.com/AgenticGovernance/tractatus-framework#readme"
<a href="https://github.com/AgenticGovernance/tractatus#readme"
target="_blank"
rel="noopener noreferrer"
class="flex items-center gap-2 p-2 rounded-lg hover:bg-gray-50 transition group">
@ -569,7 +606,17 @@
</aside>
<!-- Main Content -->
<main class="lg:col-span-3">
<main id="document-viewer-main" class="lg:col-span-3">
<!-- Back to Documents Button (Mobile Only) -->
<button id="back-to-docs-btn"
class="mb-4 items-center gap-2 px-4 py-2 bg-white border border-gray-300 rounded-lg shadow-sm hover:bg-gray-50 transition"
aria-label="Back to documents list">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7"/>
</svg>
<span class="font-medium text-gray-700">Back to Documents</span>
</button>
<div id="document-content" class="bg-white rounded-lg shadow-sm border border-gray-200 p-8">
<div class="text-center py-12">
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
@ -811,13 +858,13 @@
<!-- Version Management & PWA -->
<script src="/js/version-manager.js"></script>
<script src="/js/components/document-cards.js?v=0.1.0.1760254958072"></script>
<script src="/js/docs-app.js?v=0.1.0.1760254958072"></script>
<script src="/js/docs-search-enhanced.js?v=0.1.0.1760254958072"></script>
<script src="/js/components/document-cards.js?v=0.1.0.1760825529624"></script>
<script src="/js/docs-app.js?v=0.1.0.1760825529624"></script>
<script src="/js/docs-search-enhanced.js?v=0.1.0.1760825529624"></script>
<!-- Internationalization -->
<script src="/js/i18n-simple.js?v=0.1.0.1760643941"></script>
<script src="/js/components/language-selector.js?v=0.1.0.1760643941"></script>
<script src="/js/i18n-simple.js?v=1760818106"></script>
<script src="/js/components/language-selector.js?v=1760818106"></script>
</body>
</html>

View file

@ -133,15 +133,23 @@ function groupDocuments(docs) {
function renderDocLink(doc, isHighlighted = false) {
const highlightClass = isHighlighted ? 'text-blue-700 bg-blue-50 border border-blue-200' : '';
// Determine if PDF download is available
// Check if metadata indicates PDF existence (presence in slugs that typically have PDFs)
// Documents with download_formats.pdf, markdown files, or most docs should have PDFs
// API Reference docs and technical diagrams might not have PDFs
const hasPDF = !doc.slug.includes('api-reference-complete') &&
!doc.slug.includes('openapi-specification') &&
!doc.slug.includes('api-javascript-examples') &&
!doc.slug.includes('api-python-examples') &&
!doc.slug.includes('technical-architecture-diagram');
// Determine if PDF download is available and get PDF path
// First check if document has explicit download_formats.pdf
let pdfPath = null;
let hasPDF = false;
if (doc.download_formats && doc.download_formats.pdf) {
pdfPath = doc.download_formats.pdf;
hasPDF = true;
} else if (!doc.slug.includes('api-reference-complete') &&
!doc.slug.includes('openapi-specification') &&
!doc.slug.includes('api-javascript-examples') &&
!doc.slug.includes('api-python-examples') &&
!doc.slug.includes('technical-architecture-diagram')) {
// Fallback to default /downloads/ path for documents that typically have PDFs
pdfPath = `/downloads/${doc.slug}.pdf`;
hasPDF = true;
}
// Add download button styling
const paddingClass = hasPDF ? 'pr-10' : 'pr-3';
@ -153,7 +161,7 @@ function renderDocLink(doc, isHighlighted = false) {
<div class="font-medium text-gray-900">${doc.title}</div>
</button>
${hasPDF ? `
<a href="/downloads/${doc.slug}.pdf"
<a href="${pdfPath}"
target="_blank"
rel="noopener noreferrer"
class="doc-download-link"
@ -308,12 +316,37 @@ async function loadDocuments() {
}
});
// Check for URL parameter to auto-expand category
// Check for URL parameter to auto-load document or category
const urlParams = new URLSearchParams(window.location.search);
const docParam = urlParams.get('doc');
const categoryParam = urlParams.get('category');
// Auto-expand and navigate to category from URL parameter
if (categoryParam && grouped[categoryParam] && grouped[categoryParam].length > 0) {
// Priority 1: Load specific document by slug if provided
if (docParam) {
const doc = documents.find(d => d.slug === docParam);
if (doc) {
// Find and expand the category containing this document
const docCategory = categorizeDocument(doc);
if (docCategory) {
const categoryDocsEl = listEl.querySelector(`.category-docs[data-category="${docCategory}"]`);
const categoryArrowEl = listEl.querySelector(`.category-toggle[data-category="${docCategory}"] .category-arrow`);
if (categoryDocsEl) {
categoryDocsEl.style.display = 'block';
if (categoryArrowEl) {
categoryArrowEl.style.transform = 'rotate(0deg)';
}
}
}
// Load the requested document
loadDocument(docParam);
} else {
console.warn(`Document with slug "${docParam}" not found`);
}
}
// Priority 2: Load category if provided but no specific document
else if (categoryParam && grouped[categoryParam] && grouped[categoryParam].length > 0) {
// Expand the specified category
const categoryDocsEl = listEl.querySelector(`.category-docs[data-category="${categoryParam}"]`);
const categoryArrowEl = listEl.querySelector(`.category-toggle[data-category="${categoryParam}"] .category-arrow`);
@ -330,7 +363,9 @@ async function loadDocuments() {
if (firstDoc) {
loadDocument(firstDoc.slug);
}
} else {
}
// Priority 3: Default behavior
else {
// Default: Auto-load first document in "Getting Started" category (order: 1)
const gettingStartedDocs = grouped['getting-started'] || [];
if (gettingStartedDocs.length > 0) {
@ -403,12 +438,21 @@ async function loadDocument(slug) {
// Fallback to traditional view with header
const hasToC = currentDocument.toc && currentDocument.toc.length > 0;
// Check if PDF is available (same logic as sidebar)
const hasPDF = !currentDocument.slug.includes('api-reference-complete') &&
!currentDocument.slug.includes('openapi-specification') &&
!currentDocument.slug.includes('api-javascript-examples') &&
!currentDocument.slug.includes('api-python-examples') &&
!currentDocument.slug.includes('technical-architecture-diagram');
// Check if PDF is available and get PDF path
let pdfPath = null;
let hasPDF = false;
if (currentDocument.download_formats && currentDocument.download_formats.pdf) {
pdfPath = currentDocument.download_formats.pdf;
hasPDF = true;
} else if (!currentDocument.slug.includes('api-reference-complete') &&
!currentDocument.slug.includes('openapi-specification') &&
!currentDocument.slug.includes('api-javascript-examples') &&
!currentDocument.slug.includes('api-python-examples') &&
!currentDocument.slug.includes('technical-architecture-diagram')) {
pdfPath = `/downloads/${currentDocument.slug}.pdf`;
hasPDF = true;
}
let headerHTML = `
<div class="flex items-center justify-between mb-6 pb-4 border-b border-gray-200">
@ -425,7 +469,7 @@ async function loadDocument(slug) {
</button>
` : ''}
${hasPDF ? `
<a href="/downloads/${currentDocument.slug}.pdf"
<a href="${pdfPath}"
target="_blank"
rel="noopener noreferrer"
class="p-2 text-gray-600 hover:text-blue-600 hover:bg-blue-50 rounded transition"
@ -462,6 +506,9 @@ async function loadDocument(slug) {
}
}, 100);
// Mobile navigation: Add document-active class to show document view
document.body.classList.add('document-active');
// Scroll to top
window.scrollTo({ top: 0, behavior: 'smooth' });
@ -564,3 +611,15 @@ if (modal) {
}
});
}
// Mobile navigation: Back to documents button
const backButton = document.getElementById('back-to-docs-btn');
if (backButton) {
backButton.addEventListener('click', function() {
// Remove document-active class to show sidebar
document.body.classList.remove('document-active');
// Scroll to top
window.scrollTo({ top: 0, behavior: 'smooth' });
});
}