Rookery
    [idea:rookery]concept

    A rookery

    A rookery is a place where your ideas can grow. Rookeries are collections of files, entirely local and owned by you, with no vendor or cloud lock-in. When rookeries are compiled with Rheo, every idea can be rendered as a webpage, a PDF, or an EPUB, at any time. This is true also for collections of ideas, ranging from a set of ideas with the same tags to your entire rookery.

    One use case for rookery is searchable documentation sites, just like this site, which is (yep, you betcha!) itself a rookery. It can also be used as a Typst alternative for an organization site, a blog, or a research journal. More generally, rookery allows you to structure content in such a way that it is interlinked, easy to grow over time, and capable of being viewed in multiple different orders. It thus also makes a good basis for context engineering workflows and LLM-assisted development.

    [idea:getting-started]Getting started

    The easiest way to get started with a rookery is by installing Rheo, a typesetting engine based on Typst. Once you have rheo on your path, scaffold a new Rheo project:

    rheo init my_rookery
    cd my_rookery

    Add the following section to the rheo.toml configuration file at the root of the my_rookery project:

    [packages.rookery]
    releases = "freecomputinglab/rookery"

    This allows Rheo to discover the rookery packages. Rheo has a built-in development server which will incrementally update your browser as you edit source files:

    rheo watch . --html --open

    Once this is running, you are all ready to hatch your first idea. Do so in index.typ (or create a new file if you prefer):

    #import "@rookery/core:0.1.0": idea
    #idea[I want to hatch ideas with rookery.]

    You’re all set! Your rookery is now ready to nurture your every next idea.

      [idea:idea]concept

      An idea

      An idea in a rookery is written in Typst. Every idea can be referenced by any other idea or page in the same rookery.

      Each idea also gets a standalone page that will show its context—where it was first hatched—and its backlinks—the set of other ideas and pages that reference it—in its footer. Try clicking on this idea’s name above (the [idea:idea] text), to see its standalone page as an example.

      Ideas can be hyperlinked to other ideas, or they can be interpolated as windows onto the original idea. Clicking on the title of a window will unfold the idea within your current context. Clicking on the idea’s name will take you to the idea’s standalone page.

      Try unfolding these windows below by clicking on their title panel to learn more.

      [idea:hatching-ideas]Hatching ideas

      Ideas are designed so that you can always hatch new ones without ceremony. The #idea function at its most basic takes the content of an idea.

      #idea[Hatch a new idea.]

      You can think of an idea as an evergreen note, as an atomic unit of thought, as a generalization of the Orgmode TODO, or simply as a referenceable and taggable block of content. All ideas in your rookery are conceptually networked together so that you can treat a rookery as an associative archive.

      An idea might be a short note you want to jot down, a record relating to a person or a place, a blog post, an academic paper, a todo item, a meeting note, a note associated to a citation, a tracking issue for a call for proposals, or any other kind of structured content fragment you can imagine. You can specify custom metadata structures for certain types of ideas using tags.

        When you sit down at your computer, ready to jot down some ideas or write a piece, it wouldn’t be nice to have to always explicitly wrap your content in #idea blocks. You can use the #ideate function in combination with a Typst #show rule to implicitly parcel your writing into ideas:

        #import "@rookery/core:0.1.0": ideate
        #show: ideate
        
        = Let's get rolling
        Straight into it without ceremony.

        This will wrap your writing as an idea, taking the #document.title as its name. Importantly, you can parameterize the #ideate function to parcel your writing out into ideas differently:

        #show: ideate.with(separator: heading.where(level: 2))
        
        == First idea
        The body of my first idea.
        
        == Second idea
        The body of my second idea.

        This is useful when you want to take writing in Typst that isn’t structured as ideas and import it into a rookery, as you don’t have to retrofit #idea blocks throughout: you just need to design the right #ideate show rule. Ideation also works nicely with Rheo spines, as it means that you can organize your ideas using files and folders and still have them appear in the flat idea space so that they can be further organized using tags.

          You can add tags to any idea. Tags work as a lateral filter across many ideas that you can use to group windows on them, group outlines, or filter ideas in a search modal.

          #idea(
            "meeting-notes",
            tags: ("draft", "review"),
          )[ ... ]
          
          /* Window over all ideas with the tag "draft" */
          #window(tagged: "draft")

          Beneath their appearance as simple strings that can be used to organize ideas, tags are implemented as hash maps using strings as keys and abstract types as values. This means that you can use them to build up complex data structures that function as metadata for more structured ideas:

          #idea(
            title: [An idea with complex metadata],
            tags: (
              "opened": datetime(year: 2026, month: 9, day: 19),
              "expected-length": duration(days: 2, hours: 1, minutes: 30),
              "details": ("a": 1, "b": 2),
            ),
          )

          For an example of treating tags as metadata to create more structured idea variants, see @rookery/timeline.

            A footnote belongs to the idea in which you write it, just as citations do. So that rookery can track them correctly, you need to use the footnote function imported from rookery in ideas, rather than the Typst native function. (Using Typst’s own #footnote will cause a compilation error.)

            #import "@rookery/core:0.1.0": idea, footnote
            #idea("etal")[
              A claim#footnote[The evidence.] worth qualifying.
            ]

            Footnote numbering is idea-local. This means that there may be two footnotes labeled 1 on the same page, if two ideas with footnotes are hatched in that context.1

            Footnote listings occur at the end of each idea.2 On a standalone page, footnotes appear before the context and backlinks listings.

            A footnote written outside an idea’s context proxies the native Typst function so that it behaves normally.

            What you write inside a footnote belongs to the idea as well, and not to the footnote. A citation in a footnote is claimed by the surrounding idea and listed in its References,3 and a window or an @idea: reference in a footnote registers its backlink exactly as it would in the idea’s own prose.

            Footnotes

            1. ^ This idea’s own first footnote. The idea below has one too, also numbered 1.
            2. ^ Ideas carry their footnotes everywhere they appear in the rookery—their hatching context, their standalone page, and their windows. The only exception to this is where a window on an idea carries a limit which truncates the content.
            3. ^ As this one is (Mädje 2022). Look for it in this idea’s References block below, rather than in the footnote itself.

            References

            • Mädje, Laurenz. 2022. “A Programmable Markup Language for Typesetting.” Master's thesis.

              A citation belongs to the idea in which you write it, just as footnotes do. Bibliographies, like footnotes, are produced at the end of an idea.

              In contrast to footnotes, however, all citations in a rookery draw from a global bibliography that is configured once like so:

              #show: rookery.with(bibliography: arguments(
                bytes(read("references.bib")),
                style: "chicago-author-date",
              ))

              You must use bytes(read(...)) rather than a path to pass a reference file, but rookery bibliographies otherwise work the same as Typst bibliographies.

              Once a rookery is configured with a bibliography, you can cite as you naturally would in Typst (Mädje 2022). Bibliographies will appear at the bottom of every idea with a citation under a ‘References’ heading.

              Citation numbering is rookery-wide, which means that numeric styles will not be scoped to each idea. (An idea with one citation may show it as [7], for example, if it is the 7th citation in the rookery.) For this reason we recommend using citation styles that don’t employ numbers, such as "author-date".

              References

              • Mädje, Laurenz. 2022. “A Programmable Markup Language for Typesetting.” Master's thesis.
              [idea:referencing-ideas]Referencing ideas

              You can reference an existing idea by creating either a hyperlink or a window. Both kinds of references use the idea’s name, which is unique in a global namespace.

              Names are normal Typst labels, meaning that compilation will fail if there is a duplicate. To ensure that rookery’s labels don’t easily clash with ones you create yourself, the prefix idea: is prepended to all of your idea names. You can customize this prefix when you configure rookery.

                Hyperlinks are the lowest-touch way to reference an idea in rookery, and are implemented as regular Typst references. Say you have an idea:

                #idea("first-idea", title: [My first idea])

                The following three bullets all produce the same result: a hyperlink that reads ‘My first idea’ to the idea’s standalone page.

                  [idea:standalone-idea-pages]

                  Standalone pages for ideas

                  When you compile a rookery with Rheo, each idea that you declare will produce its own standalone page. When you link to an idea using a hyperlink, by default it will link to that idea’s standalone page. (To link to the page in which the idea was actually declared, see #hyperlink.)

                  The standalone page will be minted at ideas/<idea-name>.html, where <idea-name> is the name you give explicitly or the one that is implicitly generated. The ideas directory is configurable through the prefix setting (see the site-config reference).

                  #import "@rookery/core:0.1.0": hyperlink
                  - @idea:first-idea
                  - @idea:first-idea[My first idea]
                  - #hyperlink(<first-idea>)[My first idea]

                  Note that you must use the idea: prefix (which you may customize) when you are using references in the Typst namespace. When using the #hyperlink function imported from rookery, you may omit the prefix if you choose.

                  Creating a hyperlink to an idea will add it to that idea’s set of backlinks.

                    Windows can be used to interpolate the entirety of an idea’s content into a different context. They are useful in home pages or other sections that aggregate content.

                    Fundamentally, windows are a form of augmented hyperlink. They take their name from Ted Nelson’s notion of the transpointing window as they allow you to see the content on either side of the link (like a window).

                    Say you have an idea:

                    #idea("first-idea", title: [My first idea])

                    You can produce a window on this idea like so:

                    #import "@rookery/core:0.1.0": window
                    #window(<first-idea>)

                    Note that we do not need the idea: prefix. Like #hyperlink, #window is a function imported from rookery that already knows which namespace to look in.

                    By default, this window will be unfolded, showing the full content of the idea.

                      [idea:window-depth]

                      Unfurling windows

                      An idea that windows onto itself, or two ideas that window onto each other, can constitute an infinite loop that could therefore unfurl forever. In order to prevent this, rookery has a notion of window unfurl, which is set to 1 by default.

                      When a window is called at a level of recursion greater than the unfurl budget, rookery renders a call to #window as a link to the idea’s standalone page rather than as transcluded content. It’s best to think of window unfurl as a multiplier, as the amount of work rookery needs to do multiplies when you raise it.

                      You can set the unfurl budget per window, or site-wide:

                      #show: rookery.with(window-unfurl: 1)
                      #window(<first-idea>)
                      #window(<first-idea>, unfurl: 2)

                      Here is a window on this selfsame idea. Because this documentation uses the default unfurl of 1, it only recurses as a window once, and then bottoms out as a link:

                        You can outline the ideas in a context like so:

                        #import "@rookery/core:0.1.0": idea, outline
                        #outline(target: idea)

                        Typst’s own way of outlining something other than headings is to name it with target:, so rookery overloads that call rather than asking you to learn a second one. target: idea outlines your ideas; every other target—the default heading, a figure kind, anything else—passes straight through to Typst’s #outline unchanged.

                        This outline is derived from how you nest #idea hatchings, and lists every idea in the rookery by default—pass scope: "page" to narrow it to only the ideas written on this page. As windows are only echoes of ideas that live elsewhere, they are not included.

                        The ordering of ideas across site-wide outlines will hew to the Rheo spine’s order (which is lexicographic by filename by default), with index.typ first.

                          [idea:auto-naming]

                          How are ideas auto-named?

                          When you declare an idea without a name, rookery will generate one for you. Because a compiled rookery is a pure function of the files that are on disk, however, auto-names may drift as you move and edit the idea.

                          Rookery will make a best effort to give your idea a unique name through the following heuristic:

                          1. Use a kebab-case version of the idea’s title.
                          2. For ideas without a title, construct a name from recombining and hashing the body content.

                          This heuristic is not guaranteed to produce unique names for all ideas. When you have two ideas without a title and the same body content, for example, the auto-name for both ideas will be identical. When two or more ideas have the same name, your rookery’s compilation will fail.

                          Due to the lack of a uniqueness guarantee and the instability of auto-naming, we recommend explicitly naming all ideas in your rookery. If you don’t care about choosing your ideas’ names, you can consider simply copying the auto-name from the browser and pasting it into Typst. Auto-naming exists so that inventing a name for each idea does not gate its inclusion in the rookery, but it should be treated as a provisional band-aid rather than a load-bearing mechanism.

                          This site is a rookery. Everything you see here exemplifies rookery features such as windows, hyperlinks, and outlines.