Two doors onto the same companies: /hype for the pitch, /investments for the reservations
The same two companies, rendered twice on purpose — one surface that only enthuses, one that shows the scorecard. The split turned out to run deeper than hiding a chip.
Two doors onto the same companies: /hype for the pitch, /investments for the reservations
Why Care?
There are two honest things to say about a company you have researched, and they do not belong on the same page.
One is the reason it is worth someone's attention — the adoption number that is genuinely surprising, the wedge nobody else has. The other is the reservation, the open question, the number you wrote in the scorecard. Both are true. Putting them side by side produces a page that does neither job: too hedged to share, too promotional to trust.
So the memos now render through two surfaces that are deliberately near-duplicates. /hype-machine is the short list of companies worth telling someone about. /investments is every memo written, including the ones that ended in a pass, with the full document behind it. Same collection, same card component, opposite register.
The split runs deeper than hiding a chip
The first pass hid the scorecard facts from the hype card and called it done. That was wrong, and the copy proved it — the my_take text for Chroma ended with "the monetization is unproven. I score it CONSIDER at 3.1."
Hiding the chip while keeping that paragraph leaves the hedge on the page in prose form. The problem was never the chip; it was that one field was being asked to carry two voices.
So the comment field split by register:
hype variant | memo variant | |
| Long-form comment | hype_note — "Why it matters" | my_take — "My take" |
| Stage / Verdict / Score | — | shown |
| Draft-status pill | — | shown |
| Call to action | "Why I'm into it" | "Read the memo" |
hype_note never carries a hedge or a number. my_take keeps the reservations and lives next to the analysis that earned them. The variants of CompanyCard differ by what they withhold, not by styling — which is the part worth remembering when the next surface gets added.
OpenGraph comes from the company, not the memo
A memo is a document about a company; it has no imagery of its own. So each memo carries a company_url, and the card's picture and self-description come from fetching that URL at build time.
That fetch runs through LFM's OGDispatcher rather than a hand-rolled fetch, which buys four things that make a build-time network call safe: an on-disk cache, retries with backoff, a concurrency cap, and rate-limit accounting. It shares src/data/og-cache.json with the link-preview fetch under /promote, so a URL warmed by either path is warm for both.
One dispatcher per build, not per page. Astro evaluates each page module independently, so a dispatcher constructed in page frontmatter would produce a fresh instance — and a fresh in-memory cache — for every route, with four routes racing to write one JSON file.
All six routes are prerender = true. This site is output: 'server', and per the 2026-08-17 serverless entry a function's filesystem is read-only and discarded per invocation. A fetch that writes a cache has to happen at build time or not at all.
Frontmatter og_title / og_description / og_image override whatever comes back, so a company site that blocks bots or describes itself badly is corrected per-company rather than by disabling the fetch for everyone.
The memos carry a Table of Contents the web should not show
Each memo authors its own inline ## Table of Contents — roughly fifty nested list items. That exists for a reason: the same file is exported to PDF, where there is no sidebar and no scroll-spy, and a printed page genuinely needs its own contents list.
On the web it is worse than redundant. It is a second, static outline sitting above the live ToC rail, and it silently disagrees with the rail the moment a heading is edited.
strip-toc.ts removes the ToC heading and every sibling until the next heading at the same depth or shallower — the heading, the list, and the --- that framed it. The markdown is untouched, so the PDF export keeps its contents page.
It runs after processor.run() on purpose. LFM's remarkHeadingIds has populated tree.data.headings by then, so the tree and the rail get filtered against one predicate in one place. A plugin appended with .use() would run after that pass anyway and would still leave "Table of Contents" sitting in the rail.
A build failure worth naming, because it will come back
Neither memo would build. Both referenced their deck screenshots as io/lossless/deals/<Deal>/outputs/.../*.png — paths that resolve on the machine that generated the memo and nowhere else. Astro treats a relative path in a collection entry as an asset to resolve and fails the entire build when it cannot find one.
All 22 referenced files did exist under memopop-orchestrator/io/, so they were copied into public/investments/<slug>/deck-screenshots/ and the 30 references rewritten to absolute /investments/... paths. Absolute paths fix both halves at once: the file exists, and Astro leaves /-rooted paths alone as public assets.
Two things to know about that fix. The originals came from mismatched locations — TrustedRouter's from outputs/TrustedRouter-v0.0.2/, Chroma's from a .cache/deck-<hash>/ directory — which suggests the orchestrator's path emission is not stable. And the next memopop regeneration will re-emit io/... paths and break the build again. The durable fix is either the orchestrator emitting site-relative paths or a rewrite step at ingest. Both are memopop-side and were left alone.
Schema deliberately stays permissive
investmentMemos keeps every field optional plus .passthrough(), matching how contextV treats context-v frontmatter. These files are generated by memopop-orchestrator, whose vocabulary evolves; a hard schema would fail the build on a field the pipeline added, which is the wrong failure mode for content authored elsewhere.
The company-layer keys added here — company_url, hype, hype_note, my_take, stage, verdict, score — are hand-authored and the orchestrator does not write them. They describe the company rather than the document.
Files changed
Content and schema:
src/content.config.ts— company-layer keys oninvestment-memossrc/content/investments/memos/{Chroma,Trusted-Router}/— company frontmatter, image paths rewritten
Data layer:
src/lib/investments/types.ts— theInvestmentshape both surfaces render fromsrc/lib/investments/og.ts— one build-time OG dispatcher per buildsrc/lib/investments/companies.ts— loader plus every fallback chain, so the two pages cannot drift on what "the title" meanssrc/lib/investments/strip-toc.ts— authored-ToC removal
Surfaces:
src/components/investments/CompanyCard.astro— adapted from lossless.group'sToolCardsrc/pages/hype-machine/{index,[investment]}.astrosrc/pages/investments/{index,[investment]}.astro
Also included:
/design-system/cardscatalogsCompanyCardand its degraded states — no OG image, frontmatter-only, accent set — alongside the same fixture rendered as both variants, so the withholding is inspectableHeader nav gains "Hype"
