{
"path": "/posts/2025/zoomable-document/index",
"site": "at://did:plc:mracrip6qu3vw46nbewg44sm/site.standard.publication/self",
"$type": "site.standard.document",
"title": "Zoomable Document",
"updatedAt": "2025-12-30T23:07:05.350Z",
"description": "Zooming in and out of a document to view levels of detail, like a map for content",
"publishedAt": "2025-01-23T01:50:48.000Z",
"textContent": "import { ZoomableDocument } from '@components/layout/ZoomableDocument';\nimport Level2Content from './_level_2.md?raw';\nimport Level3Content from './_level_3.md?raw';\nimport Level4Content from './_level_4.md?raw';\nimport Level5Content from './_level_5.md?raw';\n\nexport const zoomLevels = [\n { level: 2, content: Level2Content },\n { level: 3, content: Level3Content },\n { level: 4, content: Level4Content },\n { level: 5, content: Level5Content },\n];\n\n<ZoomableDocument client:load levels={zoomLevels}>\n\nCredit\n\nI can't actually remember if I got inspiration for this idea from Amelia's presentation, but watching that again recently, it seems likely so I am crediting her for it here.\n\nThe Spark\n\nI'm trying something unusual with this post.\nI think it's going to be a lot of fun.\nI was listening to Nabeel Hyatt and Dan Shipper's conversation, where they mentioned _code has become content_.\nThis resonated with me because I've lately found, specifically with the projects on this site, that writing and building have started to go hand in hand for me.\nThe prose I write to describe my ideas often becomes the prompts I use to build them and vice versa.\n\nPowerful models and their code generating abilities are incredible synthesizers for ideas.\nPrior to having a LLM at hand to write most of the code, I wouldn't have the energy to prototype or attempt building many of them.\nMost didn't feel worth the effort or I'd get stuck or frustrated and move on.\nWith that context, I'm going to (attempt to) build this interactive post by writing the post.\n\nStarting from Scratch\n\nAs I write now, I have Cursor open and I am writing in a file at src/content/notes/2025/document-?/index.mdx in my Astro project.\nThe folder and post are currently called document-? because I don't really know what this concept is yet, but I needed to start somewhere.\nMaybe it's called something else when you're reading it.\nMy site has interactive Astro and React components at src/components.\nI can include these components in my .mdx content which has been a really enjoyable way to play around with ideas.\n\nUnderstanding Content Structure\n\nFor this post, we are building an experience that allows the reader to interact with the content of a post at multiple levels of detail and specificity.\nMost content has at most just a few levels of content.\nThe first are headings.\nThese include things like titles and section headers often denoted by # tags in markdown.\nThese titles and headers can be summarized into a table of contents to give the reader an overview of what's in an article or the ability to jump around or permalink to certain parts.\nA second level of content is the linear prose or the body of the work.\nThis is a bit of a kitchen sink category, including prose, lists, code, images and everything that composes most of the written work.\nFinally, a third level of content includes things like asides, footnotes and citations that appear next to or after the main prose.\nSometimes these are off to the side of content or aggregated all together at the end.\n\nEnhancing Navigation with LLMs\n\nWith LLMs, we can create summaries that provide at-a-glance skimmability and arbitrary levels of specificity.\nImagine zooming in and out of a document like you do when using an app like Google Maps.\nIf you navigate to New York City and zoom into a particular neighborhood, you can see the roads and the locations of the buildings.\nThis zoomed in view is like reading the original prose of a blog post.\nAt this level of zoom, you are limited in how much you can see.\nYour browser, phone or field of view is only so large.\nYou can't look at every road in the city at the same time.\nZoom out further and we can see the boroughs or even other states, but then we'd see very few roads.\nThere's simply too much detail to show when zoomed out at this level.\nProse is like this as well but most \"maps\" of prose are relatively static.\nIn practice, we actually spend relatively limited effort making our prose accessible at different levels of zoom.\nWhat would that even look like?\n\nLimitations of Traditional Navigation\n\nLike a map, we may want to zoom out and view an entire piece of written text from a high level, understanding its broad features.\nToday, the closest thing to something like this is a table of contents.\nIn the best case, the author has thoughtfully titled their chapters in a way that actually gives us something akin to a high level overview.\n\nAnother area that can help is a glossary.\nWith the right vocabulary, we can locate references to specific items of interests.\nUnfortunately, most glossaries are limited beyond close to perfect string matches.\nThey include things like technical terms, acronyms, and specialized vocabulary.\nBut they don't allow us to find where certain concepts are mentioned or where certain motifs occur, at least not flexibly.\nGlossaries rely on the foresight of the author to anticipate how a reader might want to navigate the prose.\n\nDigital documents contain many improvements.\nWe can \"find\" any word or phrase across an arbitrarily large document.\nBut it's still fuzzy how you might find all the places in a written work where a character displays \"kindness\", for example.\nIn a literary work, the author doesn't come out and say when a character is kind.\nThey show the character's kindness through their actions.\nWe can't just run \"find\" on the word \"kind\".\nWe'll miss a lot.\n\nBuilding the Zoomable Document\n\nLet's return to what a zoomable document looks like.\nWe have our street level view.\nThat is the unmodified document.\nWe see all the words, images, code, asides, footnotes, everything.\nThe raw, unedited text as the author created it.\n\nLet's zoom out.\nNow we're seeing neighborhoods.\nEach neighborhood represents a chunk of content that's roughly smaller than a major heading but larger than individual paragraphs.\n\nMarkdown supports 6 heading levels.\nNot everyone uses all 6 levels but some people do.\nSo when we zoom out, we're zooming out relative to the levels of content the author has provided, but we can't necessarily rely on these \"cliff notes\".\nIn fact, we have to assume the content won't already contain a \"roadmap\" for the reader.\nThat is the utility of what _we_ aim to provide with our \"zoomable document\".\n\nTechnical Implementation\n\nHow can we construct this zoomable document?\nWe'll first need to take a look at the raw content that exists at the street level.\nFrom here, we will need to compute summaries for each different level of zoom.\nWhen we zoom out, content at the previous level fades away and content at the next level fades into view.\nAs we zoom further and further out, the content is condensed into summaries that capture the essence of the lower levels, giving the reader the ability to quickly scan through these summaries, then zoom into areas of interest, like a dynamic map.\n\nI store zoom levels as separate markdown files in the same directory as the post. This keeps the main post file clean and makes it easier to manage different zoom levels independently. The files are imported directly into the MDX file using Vite's ?raw suffix, which imports the markdown content as strings.\n\nThe file structure looks like this:\n\nThe content is passed to the React component using this schema:\n\nimport Chat from '@components/prose/Chat.astro';\n\nTo generate content for those levels, with this (or any) document as context, I use the following prompt\n\n<Chat\n model=\"claude-3-5-sonnet-20241022\"\n messages={[\n {\n role: 'user',\n content:\n \"Using markdown (including headers, list and code if useful) given the content at Level 1, write Levels 2-5, maintaining the author's voice and provide increasingly general levels of specificity as the levels increase. Don't just summarize. Provide some of the same details as the original text at the lower levels. Do more summarizing at the higher levels. Use your best judgment to determine which details are relevant at each level. Remember, lower levels are like street and neighborhood views on Google Maps where you can see roads and buildings. Higher levels are like county, state and even country views where we get more of the general geography of a space but see fewer details.\",\n },\n ]}\n/>\n\nThe results have been both interesting and challenging to achieve.\nI created the first version of the ZoomableDocument React component by using an early version of this prose as the prompt and giving an LLM instructions to \"build that\".\nFrom there, I continued writing more in prompts and here to explain what approaches were working well and refined the prose further.\nThe above prompt allows me to easily use an LLM to regenerate the higher level summaries whenever I make edits to this Level 1 version.\n\nTo create a zoomable document:\n\n- Write markdown content normally in the .mdx file\n- Generate zoom levels using a language model with the raw post as context using the prompt above\n- Save each zoom level as a separate markdown file in the same directory\n- Import the zoom levels to the MDX file:\n\n- Wrap your content in the ZoomableDocument component with the levels prop\n\nIn the future, I could find a way to automatically load the levels and wrap the content in the ZoomableDocument component.\n\nFuture work\n\nGiven the recent release of Claude Citations, I'll probably revisit this concept introducing explicit linkages between the summarized content and the source text.\n\n</ZoomableDocument>",
"canonicalUrl": "https://www.danielcorin.com/posts/2025/zoomable-document/index"
}