research-evidence-reader

Inside the youteacher_analyze app there is a small evidence-browsing layer: a set of /data/* routes, each of which takes one file of scraped research material off disk and hands it to a viewer component that knows how to display that shape of data. The point is to make the raw material behind the business documents — forum posts, comparison write-ups, structured analysis — readable in the browser without a database or an API in front of it.

Reading files off the server

A data route is a plain Next.js server page. It builds an absolute path with path.join(process.cwd(), ...), reads the file synchronously with fs.readFileSync, and passes the result down as props. The Reddit-evidence route reads a JSON file out of public/data/, JSON.parses it into an array of posts, and renders ForumPostsViewer. The platform-comparison route reads a Markdown file out of public/analysis/ as a string and renders AnalysisReportViewer. Both wrap their viewer in a narrow page shell whose only chrome is a "← Back to Business Plan" link home. So the file system is the data store here: to publish a new piece of evidence you drop a file into public/ and point a route at it.

Two ways in, on purpose

There are actually two loading strategies in this layer, and they coexist deliberately. The server routes read the file at render time on the server (fs.readFileSync) — the visitor's browser never fetches the file itself. The alternative is DataViewer, a collapsible client widget that does nothing until clicked, then fetches a URL on demand, and either pretty-prints it (as JSON) or shows it raw (as Markdown) inside a scrollable <pre>. The server route is for evidence that anchors a page; the client DataViewer is for evidence you only want to peek at inline, loaded lazily so it costs nothing until someone opens it.

Specialized viewers vs. generic fallbacks

The viewer components split along the same line. ForumPostsViewer is shaped to one kind of data: it takes an array of forum posts, offers a client-side search box that filters across title, content, body, and author, paginates twenty at a time, and renders each post as a card with an index badge, author, date, title, truncated body (cut at 500 characters), upvote and reply counts, and a link to the original. Its ForumPost type marks every field optional and carries an index signature, so posts scraped from different sources with different shapes all render without breaking. AnalysisReportViewer is shaped to analysis reports: it splits the Markdown on ## headings into sections and tags each section by keyword — "pain point", "evidence"/"quote", "frequency"/"severity" — giving pain-point sections a red border and each type its own icon, then renders the body with react-markdown and remark-gfm.

Behind those two sit the generic fallbacks: JsonReader renders arbitrary JSON (an array becomes a list of numbered entries, an object becomes labeled key/value rows, and nested objects are stringified into a <pre>), and MarkdownReader renders arbitrary Markdown. The specialized viewers make one dataset legible and pleasant; the generic ones make any dataset at least viewable.

Two Markdown renderers

Worth noting because it is a real inconsistency in this layer: Markdown is rendered two different ways. AnalysisReportViewer uses the react-markdown library with GitHub-flavored-Markdown support and per-element style overrides. MarkdownReader instead runs a hand-rolled chain of regular expressions that turns headings, bold, italics, links, lists, code, blockquotes and rules into an HTML string, and injects it with dangerouslySetInnerHTML. The regex parser is lighter but far more limited and trusts its input; it is only ever fed evidence files that live inside the repository, not anything a visitor supplies.

Inside this component

about this entry

One of sijie's wiki entries. The AI on this site is grounded in the same corpus and answers in sijie's voice, with citations back to entries like this one — answering costs sijie money, so it waits behind a code: enter an access code →