Markdown tabs stuck loading — blob fetch uses display string as revision #172

Closed
opened 2026-09-06 13:33:01 +00:00 by crueber · 3 comments
Owner

Markdown tabs stuck on "loading..." — blob fetch uses display SHA string as revision

On a Tree page with markdown files, the tabs render but content never loads (loading…), and the tray shows: SHA:85AB5FBCC2BBCAB038377FD84741687BB6A19C64:BLOB:README not found: …85ab5fbcc….

Root cause (from the error text — verify in code): the tab body fetch builds the blob request with the literal header-pill display string (SHA:85AB5FBCC…, uppercased, with a SHA: prefix and what looks like a BLOB:README key fragment) as the revision instead of the resolved ref/commit sha for the tree being viewed. The blob endpoint 404s because no such revision exists.

Fix

  • Resolve the viewed ref → commit sha (the same resolution the tree listing itself uses) and fetch blob content with {sha, path} through the existing blob endpoint/cache key; never interpolate UI display strings into API paths.
  • Keep the per-segment path encoding from #171; keep lazy-per-tab fetching.
  • Add a regression test pinning the fetch args (revision is the resolved 40-hex sha, path is the tab file) so display-string regressions can't recur.

Acceptance criteria

  • Tabs load content on first paint (README) and on switch; no tray errors.
  • node --test green; browser check of a doc-heavy dir both themes, zero console errors + zero toasts; no new deps.
# Markdown tabs stuck on "loading..." — blob fetch uses display SHA string as revision On a Tree page with markdown files, the tabs render but content never loads (`loading…`), and the tray shows: `SHA:85AB5FBCC2BBCAB038377FD84741687BB6A19C64:BLOB:README not found: …85ab5fbcc…`. Root cause (from the error text — verify in code): the tab body fetch builds the blob request with the literal header-pill display string (`SHA:85AB5FBCC…`, uppercased, with a `SHA:` prefix and what looks like a `BLOB:README` key fragment) as the revision instead of the resolved ref/commit sha for the tree being viewed. The blob endpoint 404s because no such revision exists. ## Fix - Resolve the viewed ref → commit sha (the same resolution the tree listing itself uses) and fetch blob content with `{sha, path}` through the existing blob endpoint/cache key; never interpolate UI display strings into API paths. - Keep the per-segment path encoding from #171; keep lazy-per-tab fetching. - Add a regression test pinning the fetch args (revision is the resolved 40-hex sha, path is the tab file) so display-string regressions can't recur. ## Acceptance criteria - [ ] Tabs load content on first paint (README) and on switch; no tray errors. - [ ] `node --test` green; browser check of a doc-heavy dir both themes, zero console errors + zero toasts; no new deps.
Author
Owner

Fixed by PR #173 (branch fix/issue-172). Root cause: DocTabs built its blob revision as t().sha ?? t().ref — the fallback interpolates a UI display string (full ref name) into the blob route's single-segment {rev}, splitting the route and 404ing every tab onto loading... forever. The SHA:... chip text is CSS-uppercased cache key (same misread class as #150), not the revision. Fix passes rev={t().sha} through the pure docFetchArgs gate (40-hex sha + encoded path, null = do not fetch); regression tests pin the fetch args. node suite 361/361 green; no browser drive per task constraints.

Fixed by PR #173 (branch fix/issue-172). Root cause: DocTabs built its blob revision as t().sha ?? t().ref — the fallback interpolates a UI display string (full ref name) into the blob route's single-segment {rev}, splitting the route and 404ing every tab onto loading... forever. The SHA:... chip text is CSS-uppercased cache key (same misread class as #150), not the revision. Fix passes rev={t().sha} through the pure docFetchArgs gate (40-hex sha + encoded path, null = do not fetch); regression tests pin the fetch args. node suite 361/361 green; no browser drive per task constraints.
Author
Owner

Review of PR #173 (fix/issue-172: tab fetch uses resolved revision) — verified in scratch worktree at origin/fix/issue-172 (878cc72), worktree removed afterward. Main worktree left untouched (still clean on main).

GATE (web/src/lib/doctabs.js:111-131): SHA_RE=/^[0-9a-f]{40}$/ admits ONLY 40-lowercase-hex — ref names with slashes, bare 'main', 12-char short shas, uppercase shas, 'SHA:'-prefixed chip text, empty/null/undefined all fall to null (rev is normalized via String(rev ?? '') before testing). Correct per case.

NULL-REV PATH (web/src/pages/Tree.jsx:99-112): selArgs() null → useData key 'doctabs:none', fetcher returns Promise.resolve(null) WITHOUT calling repoClient.blob → no request, and since no rejection reaches useData's start(), reportError (web/src/lib/data.js:121) never fires → no tray spam. Render falls to the existing 'loading…' fallback (Tree.jsx:146). Verified by reading, no browser (per instructions — node tests + reasoning only).

NO RECURRENCE OF '?? t().ref': grep over web/src/ finds zero '?? t().ref'/'?? t().sha'. Tree.jsx:215 now passes rev={t().sha}; the ONLY repoClient.blob call for tabs (Tree.jsx:111) sits behind the selArgs()/docFetchArgs guard, and the cache key (Tree.jsx:101) uses selArgs().rev too — the gate is the single choke point. docBlobPath's only remaining caller is docFetchArgs itself (doctabs.js:130).

CACHE KEY: unchanged when rev is valid — still sha:<sha>:blob:<rawSelPath>, same shape the blob page builds in useResolved (data.js:339 sha:${r.sha}:blob:...), so the shared-entry claim holds; no fork (key still uses raw selPath(), fetch uses the encoded args.path — #171 behavior preserved).

LAZY-PER-TAB: preserved — per-sel useData, probed-readme pre-fill short-circuit (Tree.jsx:105-108) untouched, Infinity TTL untouched.

REGRESSION TESTS (doctabs.test.js:127-156): pin the contract (valid sha + encoded paths pass through; 9 display-string revs + empty names gate to null). They would fail pre-fix: docFetchArgs did not exist on main, so the import itself throws; and behaviorally the old t().sha ?? t().ref + direct blob(props.rev, ...) path fetched display strings verbatim.

MISREAD CORRECTION: confirmed — no 'SHA:'-prefixed producer in web/src (grep finds only a sha.jsx comment and the test literal). App.jsx:90 renders the useData key verbatim in .chip and ui.css:55 gives .chip Tailwind uppercase, so the doc's display-vs-fetch explanation is accurate.

LAWS: 1 OK (4 files changed only — doc + Tree.jsx + doctabs.js + test; no manifest changes, stdlib-only code); 7 N/A (no async work added; null path resolves synchronously); 8 OK (web/ + doc only, no core imports); 12 OK (12_web_ui.md decision appended in the same change, claims verified accurate above).

TESTS: full suite 'node --test web/test/unit/*.test.js' → 361/361 pass (incl. 16/16 doctabs). Note: 3 data-layer test files initially failed in the scratch worktree with ERR_MODULE_NOT_FOUND solid-js because worktrees don't carry the ignored web/node_modules; re-ran via symlink to main's node_modules (read-only, removed with the worktree) → all green. 'vite build' in web/ → success (122 modules, built in ~2s).

No browser verification performed (per instructions). No changes pushed — none needed; nothing to fix.

MERGE RECOMMENDATION: ready to merge.

Review of PR #173 (fix/issue-172: tab fetch uses resolved revision) — verified in scratch worktree at origin/fix/issue-172 (878cc72), worktree removed afterward. Main worktree left untouched (still clean on main). GATE (web/src/lib/doctabs.js:111-131): SHA_RE=/^[0-9a-f]{40}$/ admits ONLY 40-lowercase-hex — ref names with slashes, bare 'main', 12-char short shas, uppercase shas, 'SHA:'-prefixed chip text, empty/null/undefined all fall to null (rev is normalized via String(rev ?? '') before testing). Correct per case. NULL-REV PATH (web/src/pages/Tree.jsx:99-112): selArgs() null → useData key 'doctabs:none', fetcher returns Promise.resolve(null) WITHOUT calling repoClient.blob → no request, and since no rejection reaches useData's start(), reportError (web/src/lib/data.js:121) never fires → no tray spam. Render falls to the existing 'loading…' fallback (Tree.jsx:146). Verified by reading, no browser (per instructions — node tests + reasoning only). NO RECURRENCE OF '?? t().ref': grep over web/src/ finds zero '?? t().ref'/'?? t().sha'. Tree.jsx:215 now passes rev={t().sha}; the ONLY repoClient.blob call for tabs (Tree.jsx:111) sits behind the selArgs()/docFetchArgs guard, and the cache key (Tree.jsx:101) uses selArgs().rev too — the gate is the single choke point. docBlobPath's only remaining caller is docFetchArgs itself (doctabs.js:130). CACHE KEY: unchanged when rev is valid — still `sha:<sha>:blob:<rawSelPath>`, same shape the blob page builds in useResolved (data.js:339 `sha:${r.sha}:blob:...`), so the shared-entry claim holds; no fork (key still uses raw selPath(), fetch uses the encoded args.path — #171 behavior preserved). LAZY-PER-TAB: preserved — per-sel useData, probed-readme pre-fill short-circuit (Tree.jsx:105-108) untouched, Infinity TTL untouched. REGRESSION TESTS (doctabs.test.js:127-156): pin the contract (valid sha + encoded paths pass through; 9 display-string revs + empty names gate to null). They would fail pre-fix: docFetchArgs did not exist on main, so the import itself throws; and behaviorally the old `t().sha ?? t().ref` + direct blob(props.rev, ...) path fetched display strings verbatim. MISREAD CORRECTION: confirmed — no 'SHA:'-prefixed producer in web/src (grep finds only a sha.jsx comment and the test literal). App.jsx:90 renders the useData key verbatim in .chip and ui.css:55 gives .chip Tailwind uppercase, so the doc's display-vs-fetch explanation is accurate. LAWS: 1 OK (4 files changed only — doc + Tree.jsx + doctabs.js + test; no manifest changes, stdlib-only code); 7 N/A (no async work added; null path resolves synchronously); 8 OK (web/ + doc only, no core imports); 12 OK (12_web_ui.md decision appended in the same change, claims verified accurate above). TESTS: full suite 'node --test web/test/unit/*.test.js' → 361/361 pass (incl. 16/16 doctabs). Note: 3 data-layer test files initially failed in the scratch worktree with ERR_MODULE_NOT_FOUND solid-js because worktrees don't carry the ignored web/node_modules; re-ran via symlink to main's node_modules (read-only, removed with the worktree) → all green. 'vite build' in web/ → success (122 modules, built in ~2s). No browser verification performed (per instructions). No changes pushed — none needed; nothing to fix. MERGE RECOMMENDATION: ready to merge.
Author
Owner

Fixed by PR #173 (review: gate + choke point + cache-key verified; 361/361 node tests), merged. Closing.

Fixed by PR #173 (review: gate + choke point + cache-key verified; 361/361 node tests), merged. Closing.
crueber added this to the v1 milestone 2026-09-10 22:27:16 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
crueber/walhub#172
No description provided.