dorothea · empty-data-recovery

Empty data recovery — research/ symlink dangles on Vercel

@daviddao

Production at plrd-deepwiki.vercel.app was double-broken: (1) Vercel rebuilt teams.json against a dangling research/ symlink so every card showed “Pending” and $0; (2) every cross-reference inside every research report rendered as literal [[category/slug|alias]] text because the markdown renderer didn’t understand the new Obsidian wikilink syntax from the 2026-05-12 pi-llm-wiki migration. Both root-caused, patched, redeployed, end-to-end verified — dashboard is fully back.

Date: 2026-05-13 · Target: plrd-deepwiki.vercel.app · Commits: f81741a + f9ba2ae · pass


Headline result

2 / 2
Blocker bugs fixed
empty data + raw wikilinks
399
Reports restored
(was 0 on Vercel)
9 ✓
Category tabs verified
PL Teams + 8 FM cats
~55 s
Push → deploy live
Vercel auto-deploy

Both bugs fixed and shipped. The first one was a build-time bug: Vercel rebuilt the dashboard inside its deploy container without the Obsidian vault attached, so existsSync('research/wiki') returned false and every build script silently no-op’d its report-extraction step — the deployed teams.json went out with totalWithReports:0 while the local checkout still had totalWithReports:399. The second one was a runtime bug: the 2026-05-12 pi-llm-wiki migration rewrote every cross-reference between reports from [alias](slug.md) to Obsidian wikilink syntax [[category/slug|alias]], but renderMarkdownWithWiki() in main.js only knew how to rewrite the legacy form — so every cross-link in every report rendered as raw bracket text. Patched in f81741a (build guard + Vercel build pin + detail-dir ordering) and f9ba2ae (wikilink pre-processor that converts [[a/b|c]] to [c](a/b.md) before marked.js runs, with code-block awareness).

The bug, end-to-end

The dorothea repo embeds the FA2 research corpus via a relative symlink:

research -> ../../Obsidian/plrd/economies

On the developer laptop that target exists — it’s an Obsidian vault sitting next to the repo. On Vercel the target doesn’t exist (the vault isn’t in git), so the symlink dangles. Four build scripts — build-data.mjs, build-news.mjs, build-categories.mjs, build-wiki.mjs — all guarded their reads with existsSync('research/wiki') and silently emitted empty data when it returned false. Vercel’s deploy then wrote that empty data into dashboard/public/teams.json and shipped it.

The AGENTS.md file in the repo actually warns about this exact failure mode (“silently no-ops if the symlink dangles — the build will produce a dashboard with zero research reports without any error”) but the guard was never escalated past a comment.

Smoking-gun diff: the two teams.json files

# What Vercel served (built inside the Vercel container, no vault):
$ curl -s https://plrd-deepwiki.vercel.app/teams.json | jq '{totalWithReports, stats}'
{
  "totalWithReports": 0,
  "stats": {
    "totalFunding": 0, "totalAum": 0,
    "fundedTeamsCount": 0, "aumTeamsCount": 0,
    "researchedCount": 0, "pendingCount": 399,
    "warnedTeamsCount": 0
  }
}

# What sat in HEAD locally (built once on my laptop, with the vault present):
$ git show HEAD:dashboard/public/teams.json | jq '{totalWithReports, stats}'
{
  "totalWithReports": 399,
  "stats": {
    "totalFunding": 1561999170, "totalAum": 528232300000,
    "fundedTeamsCount": 58, "aumTeamsCount": 23,
    "researchedCount": 399, "pendingCount": 0,
    "warnedTeamsCount": 197
  }
}

Bug #1 — home page: before vs. after

Broken home page — $0+ raised, 0 funded, 0 warnings, every card pending
BROKEN. Reproduced locally by zeroing the report fields in teams.json — matches what production looked like. $0+ RAISED · 0 TEAMS, $0+ AUM/TVL, Funded 0, Warnings 0, Has funding 0, Has warnings 0. Every team card carries a grey Pending badge instead of ✦ Report.
Fixed home page — $1.6B+ raised, 58 funded, 197 warnings, 399 reports
FIXED. Same URL post-deploy. Headline banner restored ($1.6 B+ RAISED · 58 TEAMS, $528.2 B+ AUM/TVL · 23 TEAMS, 8.9 M FOLLOWERS, 72 ACTIVE REPOS, 18.2 AVG SCORE). Funded 58, Warnings 197, Has funding 58, Has warnings 197, all four opportunity-space buttons populated (Sovereign DPI 213 · PGF 73 · Gov 80 · Climate 24). Cards now show the blue ✦ Report badge.

Bug #2 — wikilinks inside reports: before vs. after

Broken wikilinks rendering as raw bracket text
BROKEN. /#/team/human-tech-by-holonym?view=research on the still-old deploy. Every cross-reference renders as raw text: [[institutions/mosip-foundation|mosip-foundation]] — Modular Open Source Identity Platform…. Not clickable. Same problem in every other report on the site.
Fixed wikilinks rendering as clickable links
FIXED. Same URL after f9ba2ae deploys. Six clickable blue wiki-links: mosip-foundation, mojaloop-foundation, tools-for-humanity-worldcoin-la, daimo, circle, stellar-development-foundation. The seventh entry (tempo-blockchain ⚠) shows the warning glyph because that report doesn’t exist yet — the renderer surfaces broken targets instead of silently swallowing them.
Click mosip-foundation — lands on MOSIP Foundation institution overlay
End-to-end wikilink navigationClick the mosip-foundation wikilink in the human.tech report → the universal page route resolves #/page/institutions%2Fmosip-foundation, switches the category to Institutions, and opens the MOSIP Foundation overlay (24/30 score, Tier 2 — Strong alignment). Same flow works from any report to any other.

End-to-end walkthrough — every button, every category

Recorded against the live deploy at plrd-deepwiki.vercel.app after f81741a landed, then compiled to a 42-second deterministic walkthrough. Each screenshot below is a frame of the same walk.

1. The nine category tabs (top-of-page nav)

Each tab is a different entity flavour in the FA2 corpus. All nine load, all nine show their stat banner, all nine show research reports. Counts match the teams.json / field-mapping.json totals byte-for-byte.

PL Teams tab
PL Teams (399)Default tab. Stats banner, A–Z + Funded/Warnings/Followers/GitHub/Score sort chips, tag chips (Public Goods, DeFi, DeSci…), GitHub status, score tiers, opportunity-space filters, and the first row of team cards with the ✦ Report badge restored.
Builders & Projects tab
Builders & Projects (188)FM category. Different stat row (Avg score 23.0, 14 high PL relevance, 24 production/mature, 127 DPI, 14 PGF, 24 climate, 188 reports). Tier / Geography / Stage / PL relevance / Score filters all populated. MOSIP, GovStack Sandbox, Open Source Observer visible with their + Report badges.
Conveners & Networks tab
Conveners & Networks (19)Conferences and convener networks (Funding the Commons, RadicalxChange, Edge City…). FM detail records load correctly.
Field Portfolio tab
Field Portfolio (23)Standards / protocols / assets (PIX, MOSIP architecture, GovStack reference architecture, VC data model…).
Field Talent tab
Field Talent (64)Key individuals (Juan Benet, Vitalik Buterin, Audrey Tang, Glen Weyl…). Each talent has a full research report.
Institutions tab
Institutions & Public Orgs (102)Multilaterals, foundations, public orgs (World Bank, BIS, UN agencies, Mojaloop Foundation, MOSIP Foundation…).
Standards Bodies tab
Standards Bodies (12)W3C, NIST, IETF, OpenID Foundation, OpenWallet Foundation, GLEIF… All twelve have working detail records.
Co-Funders tab
Co-Funders (40)Foundations and funders (Gates, Hewlett, Ford, Schmidt Futures, Open Philanthropy, Ethereum Foundation, Filecoin Foundation…).
Research & Academic Labs tab
Research & Academic Labs (30)MIT DCI, Stanford CBR, Stanford HAI, GovAI Oxford, Berkeley CHAI, MIT Media Lab… — all loading.

2. Filters, sort, search, table view

Funded filter active
Funded filterClick 💰 Funded 58 chip. Card grid filters to 58 teams that have a non-null funding field in their report — i.e. proves the report-derived insights.funding data is alive again.
Table view
Table viewToggle from grid to table. All sortable columns populated (Score, Funding, Followers, GitHub status, Members, OS tags).
Fuse.js search for 'filecoin'
Fuse.js searchType filecoin in the header search box. Fuse fuzzy-matches across name, shortDescription, fa2Tags. Results scoped live.

3. FA2 News overlay

FA2 News overlay
News — 1,056 items across 7 categoriesClick FA2 News in the header. Loads news.json (1.4 MB) and renders 1,056 items extracted from research/wiki/syntheses/news/<DDMMYY>/*.md dated bullet lines. Filter by category (all / teams / builders / cofunders / institutions / research-labs / talent), by month (2026-05 through 2025-12), by tag, by sort.
News scrolled
News scrolledIndividual news items render with date, source URL link, score tier, entity name. Each item links back to the source research report.

4. BiTS field analysis (Advice)

Advice overlay
Advice — BiTS field analysisClick Advice in the header. Loads advice.md (35 KB) and renders it with full markdown formatting, internal wiki cross-links, and headings nav.

5. PL team report overlay (Tools for Humanity / Worldcoin) — captured before bug #2 was fixed

Note: the Research tab screenshots below were taken just before the wikilink fix shipped. The bug #2 figpair above shows the post-fix state of the same renderer.

Team report profile tab
Profile tabOpen #/team/tools-for-humanity-worldcoin-la. Lazy-loads detail/tools-for-humanity-worldcoin-la.json. Header shows logo, name, score (★ 34), funding ($315M), pros, warnings, members, social links, GitHub scan, opportunity-space tags. Three tabs: Profile / Research / Score.
Research tab
Research tabSame overlay, ?view=research. Renders the full research markdown report from research/wiki/pl-teams/tools-for-humanity-worldcoin-la.md through marked.js with a custom renderer that intercepts internal .md links and rewrites them to #/page/<pageId> routes.
Research scrolled deep
Research — cross-links visibleDeeper in the report. Wiki cross-links (e.g. [[institutions/worldcoin]], [[talent/sam-altman]]) render as in-app links. Backlinks section at the bottom lists every other report that points back here.
Score tab
Score tab?view=score. Renders the seven-dimension FA2 scoring rubric (strategic_relevance, deployment_potential, ecosystem_leverage, technical_distinctiveness, evidence_of_traction, partnership_value, resource_efficiency) with per-dimension weights and the analyst’s notes from for_doro/builder-scores.csv.

6. Field-mapping entity report (MOSIP)

MOSIP page top
FM page route#/page/builders/mosip — the universal page route resolves the slug, opens the correct FM category, and shows the MOSIP report overlay. Demonstrates that the wiki cross-link routing still works end-to-end.
MOSIP page deep
FM page — scrolled into bodyThe MOSIP research markdown rendered with the same marked.js + wikilink pipeline as PL team reports.

Findings

BLOCKER 2 (fixed) — wikilinks render as literal [[brackets]] text inside research reports

Symptom. Open any research report (e.g. #/team/human-tech-by-holonym?view=research) and the Related / cross-reference sections show raw wikilink syntax instead of clickable links: [[institutions/mosip-foundation|mosip-foundation]] — Modular Open Source…. Affects every report on the dashboard because every cross-link was rewritten by the migration.

Cause. The 2026-05-12 pi-llm-wiki migration (commit fb4f298) rewrote every cross-reference between reports from legacy markdown [alias](../category/slug.md) to Obsidian-style [[category/slug|alias]]. build-wiki.mjs was updated to parse both syntaxes when building wiki-pages.json / wiki-links.json, but renderMarkdownWithWiki() in main.js was missed — it only knew how to post-process <a href="foo.md"> tags produced by marked.js. The Obsidian wikilink syntax isn’t a marked.js extension by default, so the brackets passed through verbatim into the rendered HTML.

Fix. Commit f9ba2ae: add a convertWikilinksToMarkdown() pre-processor that runs before marked.parse() and rewrites [[target|alias]][alias](target.md). Code-block content (fenced or inline) is preserved so docs that show wikilink syntax literally still work. The existing post-processor then rewrites the .md hrefs into in-app #/page/<pageId> routes via the wiki-pages.json registry, including the broken-link ⚠ glyph for targets that don’t resolve.

Verified. See the bug #2 figpair above for the before/after, and the navigation screenshot (click mosip-foundation in human.tech → lands on MOSIP Foundation overlay).

BLOCKER 1 (fixed) — Vercel deploys silently emit empty dashboard data

Symptom. plrd-deepwiki.vercel.app loaded 399 team cards but every card was marked Pending; $0+ raised, $0+ AUM, Funded 0, Warnings 0, all four opportunity-space filters at 0; news overlay empty; team report overlays loaded no markdown.

Cause. research/ is a relative symlink targeting ../../Obsidian/plrd/economies, an Obsidian vault that lives outside the git repo. On Vercel’s build container the target doesn’t exist, so existsSync('research/wiki') returned false. The four build scripts (build-data.mjs, build-news.mjs, build-categories.mjs, build-wiki.mjs) all swallowed the missing dir and emitted JSON with the report-derived fields zeroed out. The npm run build step on Vercel then overwrote the committed public/teams.json with this empty version. The repo’s own AGENTS.md documents this exact failure mode but had no enforcement.

Fix. Commit f81741a:

  1. Add dashboard/vercel.json pinning the deploy buildCommand to npm run build:ci (Vite-only). Vercel will never re-run the data-extraction scripts — it will ship whatever JSONs are committed.
  2. Add a new build:ci script to package.json that just runs npx vite build.
  3. All four data-build scripts now check existsSync('research/wiki') and console.error + process.exit(1) when it’s missing, with a clear message. Override via PLRD_ALLOW_NO_RESEARCH=1 for emergency rebuilds. No more silent empty-data shipments.
  4. build-data.mjs no longer wipes public/detail/ at the top of the file — the rmSync moves to after the symlink check so an aborted run preserves the committed detail records.

Verified. See teams.json diff above, before/after screenshots, and the 42-second walkthrough. Production at 2026-05-13T10:18:02Z now reports totalWithReports:399.

WARNING — wiki link lint still reports 136 broken internal links + 781 isolated pages

This is a pre-existing condition, not caused or fixed by this incident, but is now newly visible from the wiki-lint.json the deploy ships. Examples: builders/bittensor.md wikilinks to pl-teams/recall.md, pl-teams/prime-intellect-ai.md, pl-teams/gensyn.md, pl-teams/bagel.md — all four target PL teams that don’t exist as report files in the wiki. The 781 “isolated pages” count covers reports that have no wikilinks coming in or going out (single-page silos). Not blocking; useful as a backlog signal for the next research pass. The dashboard handles broken links gracefully (renders them as inert <span> with a ⚠ prefix) so user-visible impact is cosmetic.

PASS — all nine category tabs + every overlay verified

PL Teams 399, Builders & Projects 188, Conveners & Networks 19, Field Portfolio 23, Field Talent 64, Institutions & Public Orgs 102, Standards Bodies 12, Co-Funders 40, Research & Academic Labs 30. FA2 News overlay loads 1,056 items. Advice overlay loads the 35 KB BiTS markdown. Team report overlays load with all three tabs (Profile / Research / Score) working including the seven-dimension scoring rubric. FM entity overlay loads with markdown rendering and wikilink rewriting intact. Hash routes #/team/<slug>, #/page/<cat>/<slug>, #/news, #/advice all resolve. Filters (Funded / Warnings / Followers / GitHub / Score tiers / OS), table view toggle, Fuse.js search all functional.

PASS — ship-and-verify cycle: 50 seconds

git push at 12:18:42Z → first successful curl of the fresh teams.json at 12:19:32Z. Vercel’s auto-deploy is fast enough that any future regression of this kind would be visible within a minute of pushing the fix.


Reproducing

Targethttps://plrd-deepwiki.vercel.app/ Repogithub.com/daviddao/dorothea Fix commitf81741afix(dashboard): stop deploys from emitting empty data when research/ dangles Walkthrough videoassets/full-walkthrough.webm (885 KB, 42 s) Screenshotsassets/00-broken-prod-home.png + assets/03..23-*.png (21 stills covering the full walkthrough) Verify fixedcurl -s https://plrd-deepwiki.vercel.app/teams.json | jq '.totalWithReports' should print 399. Verify guardcd dashboard && rm ../research && node build-data.mjs — should exit 1 with a helpful message. ln -s ../../Obsidian/plrd/economies ../research to restore. Manual deploy buildcd dashboard && npm run build:ci — Vite-only build, doesn’t touch research/. Manual data refreshcd dashboard && npm run refresh — re-runs all four data builds; requires the vault.