feat(web): land on the wiki's own index.md instead of its first page - #91
Conversation
`#/wiki` opened whichever page sorted first, which made the front door an accident of the catalog's order. It now renders docs/wiki/index.md — the page the author actually writes to say what the knowledge base is. The index links pages as files (`[Title](pages/slug.md)`), so entries are rewritten before rendering: a slug that exists becomes an in-app route, one that does not becomes a citation token, so a stale entry reads as the broken reference it is rather than a dead file link. Markdown keeps `#/…` links in the tab, and the rail carries an Index row above the catalog. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARBSaXgJ7ssW1hvpy6r64y
📝 WalkthroughWalkthroughThe wiki frontend now supports authored index pages. WikiView loads and renders ChangesWiki index navigation
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant WikiRail
participant WikiView
participant api.wiki
participant WikiIndexPane
participant api.file
participant Markdown
WikiRail->>api.wiki: load wiki metadata and pages
api.wiki-->>WikiRail: return has_index and page list
WikiRail->>WikiView: navigate to `#/wiki`
WikiView->>WikiIndexPane: render authored index
WikiIndexPane->>api.file: load docs/wiki/index.md
api.file-->>WikiIndexPane: return markdown
WikiIndexPane->>Markdown: render rewritten links and body
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@internal/web/frontend/src/components/Rail.tsx`:
- Around line 150-158: Update the wiki overview error handler in the useEffect
callback to reset hasIndex to false alongside clearing pages when api.wiki()
fails, preventing stale index state after an unsuccessful refresh.
In `@internal/web/frontend/src/components/WikiView.tsx`:
- Around line 126-131: Restrict INDEX_ENTRY_RE in rewriteIndexEntries to match
only local pages links beginning with pages/ or ./pages/, removing the arbitrary
prefix that captures external URLs. Split the markdown into code and non-code
regions, leave inline and fenced code regions unchanged, and apply the existing
replacement logic only to non-code parts before joining them back together.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 1de2a972-a8f4-402b-befb-fb933f924a97
📒 Files selected for processing (3)
internal/web/frontend/src/components/Markdown.tsxinternal/web/frontend/src/components/Rail.tsxinternal/web/frontend/src/components/WikiView.tsx
| useEffect(() => { | ||
| api | ||
| .wiki() | ||
| .then((w) => { | ||
| setPages(w?.pages ?? []) | ||
| setCategories(w?.categories ?? []) | ||
| setHasIndex(!!w?.has_index) | ||
| }) | ||
| .catch(() => setPages([])) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Clear hasIndex when the overview refresh fails.
After a prior successful load, this handler clears pages but retains hasIndex; the rail can show an obsolete Index row after the index is removed or the request fails.
Proposed fix
- .catch(() => setPages([]))
+ .catch(() => {
+ setPages([])
+ setCategories([])
+ setHasIndex(false)
+ })📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| useEffect(() => { | |
| api | |
| .wiki() | |
| .then((w) => { | |
| setPages(w?.pages ?? []) | |
| setCategories(w?.categories ?? []) | |
| setHasIndex(!!w?.has_index) | |
| }) | |
| .catch(() => setPages([])) | |
| useEffect(() => { | |
| api | |
| .wiki() | |
| .then((w) => { | |
| setPages(w?.pages ?? []) | |
| setCategories(w?.categories ?? []) | |
| setHasIndex(!!w?.has_index) | |
| }) | |
| .catch(() => { | |
| setPages([]) | |
| setCategories([]) | |
| setHasIndex(false) | |
| }) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@internal/web/frontend/src/components/Rail.tsx` around lines 150 - 158, Update
the wiki overview error handler in the useEffect callback to reset hasIndex to
false alongside clearing pages when api.wiki() fails, preventing stale index
state after an unsuccessful refresh.
| const INDEX_ENTRY_RE = /\]\(\s*(?:\.\/)?(?:[^)\s]*\/)?pages\/([^)\s/]+)\.md\s*\)/g | ||
|
|
||
| function rewriteIndexEntries(markdown: string, known: Set<string>): string { | ||
| return markdown.replace(INDEX_ENTRY_RE, (_m, slug: string) => | ||
| known.has(slug) ? `](${href('wiki', slug)})` : `](ref:[[${slug}]])`, | ||
| ) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Restrict rewriting to actual local Markdown links.
The arbitrary optional prefix matches external URLs such as https://example.com/pages/foo.md, incorrectly hijacking them into wiki routes. The global replacement also rewrites examples inside inline/fenced code. Only process pages/... or ./pages/... links outside code regions.
Proposed fix
-const INDEX_ENTRY_RE = /\]\(\s*(?:\.\/)?(?:[^)\s]*\/)?pages\/([^)\s/]+)\.md\s*\)/g
+const INDEX_ENTRY_RE = /\]\(\s*(?:\.\/)?pages\/([^)\s/]+)\.md\s*\)/g
function rewriteIndexEntries(markdown: string, known: Set<string>): string {
- return markdown.replace(INDEX_ENTRY_RE, (_m, slug: string) =>
- known.has(slug) ? `](${href('wiki', slug)})` : `](ref:[[${slug}]])`,
- )
+ const parts = markdown.split(/(```[\s\S]*?```|`[^`\n]*`)/g)
+ return parts
+ .map((part, i) =>
+ i % 2 === 1
+ ? part
+ : part.replace(INDEX_ENTRY_RE, (_m, slug: string) =>
+ known.has(slug) ? `](${href('wiki', slug)})` : `](ref:[[${slug}]])`,
+ ),
+ )
+ .join('')
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const INDEX_ENTRY_RE = /\]\(\s*(?:\.\/)?(?:[^)\s]*\/)?pages\/([^)\s/]+)\.md\s*\)/g | |
| function rewriteIndexEntries(markdown: string, known: Set<string>): string { | |
| return markdown.replace(INDEX_ENTRY_RE, (_m, slug: string) => | |
| known.has(slug) ? `](${href('wiki', slug)})` : `](ref:[[${slug}]])`, | |
| ) | |
| const INDEX_ENTRY_RE = /\]\(\s*(?:\.\/)?pages\/([^)\s/]+)\.md\s*\)/g | |
| function rewriteIndexEntries(markdown: string, known: Set<string>): string { | |
| const parts = markdown.split(/( |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@internal/web/frontend/src/components/WikiView.tsx` around lines 126 - 131,
Restrict INDEX_ENTRY_RE in rewriteIndexEntries to match only local pages links
beginning with pages/ or ./pages/, removing the arbitrary prefix that captures
external URLs. Split the markdown into code and non-code regions, leave inline
and fenced code regions unchanged, and apply the existing replacement logic only
to non-code parts before joining them back together.
What
#/wiki(no slug) renderedpages[0]— whichever page sorted first in the catalog. The wiki's front door is nowdocs/wiki/index.mditself: the authored catalog, with its prose, its categories and its order.How
WikiView.tsx— a route with no slug (or a slug that no longer exists) renders the newWikiIndexPane. It loadsdocs/wiki/index.mdthrough the same hardened/api/fileroute the pages use, lifts the leading# headinginto the pane header (so it doesn't render twice), and shows page count plus anN not in index.mdbadge with the orphans listed in the footer. Fallbacks are unchanged: noindex.md→ first page; no pages at all → the existing empty state.[Title](pages/<slug>.md)is rewritten to#/wiki/<slug>when the slug exists, and to a[[slug]]citation token when it does not — so a stale entry renders as a broken chip (no such wiki page, the same verdictcsdd wiki lintgives) instead of a dead link to a file.Markdown.tsx—#/…hrefs are in-app navigation and stay in the tab; only real external links still open one.Rail.tsx— anIndexrow above the catalog, active when no page is selected.No Go changes:
/api/wikialready exposedhas_index, and/api/filealready servesdocs/wiki/index.md.Verification
npm --prefix internal/web/frontend run typecheckclean;vite buildsucceeds (bundle not committed —internal/web/dist/.gitkeeprestored).csdd init+csdd wiki init, two pages, one stale index entry) and checked the three endpoints the pane depends on:/api/wikireportshas_index: truewith both pagesin_index,/api/file?path=docs/wiki/index.mdserves the catalog, and/api/refresolves[[cqrs]]→#/wiki/cqrswhile[[gone]]comes backbroken.🤖 Generated with Claude Code
https://claude.ai/code/session_01ARBSaXgJ7ssW1hvpy6r64y
Summary by CodeRabbit
New Features
Bug Fixes