Coming from Logseq, Outlines and Linked Mentions

Hey there,
I am also a former Logseq user and have been confused a bit about difference between tag and page references as well.

This is an interesting way to see it!

So - [name: Fluffy] #Dog basically makes this list item an instance of Dog class and assigns instance variables via attribute syntax, if I understood correctly.


1.) Do you have plans to support basic class/tag relations for querying (like part-of, or sub-types)?

For example, given Dog is an Animal (sub-type/inheritance), I would find it to be very valuable to get all Dogs , when querying for Animal objects of your space.

Continuing that simple Animal analogy, you first decide to have a space for notes about your favorite Dogs.

- dog1 #Dog
- dog2 #Dog
- dog2 #Dog

Later on, Cats and other Animals become interesting as well:

- cat1 #Cat
- cat2 #Cat
- cat3 #Cat

At some point, you’ve created so many notes, that you can’t remember all Animals. Then a query like

${query [[
  from index.tag "tag" 
  where _.name == "Animal"
]]}

just “works” and returns all sorts of Animals.
(just started with SB, probably there is a more elegant way than above query)


2.) What about the ability to declare a basic schema for classes, so that instances need to adhere to this class definition?

For example, all Dogs must have a name attribute.


This already is such a beautiful and well-crafted app, thank you so much!

It’s great to see that SB development is going strong. I’m very excited to check out v2!

Since this topic has popped up once again, I thought I’d add some thoughts I’ve been compiling over the past few months. I believe SB is a wonderful and promising project, and this is NOT meant as criticism, but is my attempt to better describe the gap being discussed in this thread. I’ve looked through the conversation here and in the new Github issue @zef has opened.


Important: Datalog Intro

Logseq uses Datalog as its graph query language.

It’s worth noting that there is a lot of confusion on Logseq forums around their complex query syntax, but after taking a few minutes to understand the syntax it is extremely powerful and flexible.

Before continuing… I strongly recommend you watch this 40 min presentation which explains how data is modeled and queried in Datalog.


TLDR;

There is a fundamental difference in the way SB vs Logseq approach how they:

  1. Parse markdown files into structured data
  2. Build their database/index
  3. Make that data available via a query language

Logseq made a structural decision up front that every line in a Logseq-generated markdown file is saved as part of a bulleted list. This mean when we parse those files it provides a simple hierarchical content structure out-of-the-box where:

  • Every “block” entity (bullet item) is just another node in the graph.
    • A block does not map 1-to-1 to a markdown file. Rather, a block is one piece of content from a markdown file.
  • A “page” entity is also just another node in the graph with some additional properties.
    • However, a page does map 1-to-1 to a markdown file.
    • All blocks parsed from the same markdown file are related to the page node as their parent.
    • The user can add additional page properties, such as a class (Book, Movie, Meeting, Client, Project, etc.) which can also be used in queries.
  • In this way a page is only related to its content through the graph relationship.
    • This provides greater flexibility in writing queries
    • Also provides a quick and easy way to associate a chunk of content (just nest any related content under a block with the foreign page link [[My Page]]) without leaving the page you are on. Now that nested chunk of content will show up in the other page’s “Linked Mentions” query.

In contrast, SilverBullet seems to take a more deliberate approach to defining its native structures which map to Markdown structures (paragraph, heading, list, etc). This makes for cleaner “traditional pages”, at the cost of flexibility. SB can only query for page level data AFAIK? As a result there’s no easy way to build similar hierarchical relations. Admittedly, I don’t know enough about SB to speak authoritatively here so keep me honest.

I believe the change that is being proposed will address a part of the ask, and is totally worth it! :heart_eyes:

However, the approach doesn’t solve for nesting non-bullet content types. This is the main gap as I see it. I don’t believe SB can offer the same flexibility without altering its approach to data modeling and its markdown format. Quick tangent; I believe this change would also enable a satisfying alternative to the current SB “Transclusions” approach.

That said, such a change would be a big breaking change, and maybe it’s just not the right approach for SB due to some other tradeoffs I haven’t considered. This is very high level and I’ve made a lot of assumptions.


Examples

Example 1: Related by Hierarchy

Here’s a basic example Logseq page

- Item 1 #my-project
	- Nested Lvl1 Item 1
		- Nested Lvl2 Item 1
	- Nested Lvl1 Item2
- Item 2

These block entities are related in their hierarchy. Each block nested under “Item 1” is related to #my-project. You could query “Item 1” and it would include all nested items in the result as part of that entity.

Having every page as one big list may sound ugly, but Logseq’s UI managed to pull this off with rich content in a way that’s not clunky, and comes with several advantages:

  • It makes sense when you think of your data as a graph. So instead of each node in the graph as a page (as in SB), each node in the graph is a block. This can be a bit of text, a link to a page, a TODO item, an image, etc.
  • Nested content is related to the parent content
    • This organizes things very similar to page headings, but instead of headings it can be a link to another node in the graph like a page or tag, tying that block of content to the other page or tag
    • Folding a block of nested content is really easy with a clear hierarchy. It’s also useful to manage transclusion of blocks because Logseq will default the render depth to 2 levels. Anything beyond 2 would be folded to save space.
  • Tags are inherited by nested items, meaning those nested items are also related to the tag even if the tag does not live explicitly on that line.

The closest equivalent I’ve seen in SB is transclusion. Since SB doesn’t have the luxury of everything built as a list with innate hierarchy, it uses the headers of a more traditional page outline. Transclusion can target a page heading which will include all content below that heading to the next heading of the same level. This is much more limiting in what can be accomplished and feels like a less organic way to create+relate content as compared to my experiences with Logseq.

Example 2: Consistent Authoring Workflow

In Logseq, I never add new content directly to pages. All new content is entered into the current day’s Journal page. In SB, I’d have to open a page, find the location where I wanted to add that content, then start typing. When the subject changes, it’s incredibly disruptive and expensive mental cost. Meanwhile, you will miss notes as the discussion continues.

Here’s a more practical example page in today’s journal in Logseq, which would live in journals/2025_05_04.md. Let’s say I had 1 meeting that day with @zef where we talked about the V2 project and some other random topics.

- [[P/Zef]]
	- [[W/SBv2]]
		- Lua is cool
		- TODO [#A] dig into v2 changes
		  SCHEDULED: <2025-05-06 Tue>
	- TODO [#A] look into recommended approach before next call 
	  SCHEDULED: <2025-05-07 Wed>

With a very simple setup, we can streamline all new data entry in the journals:

  • Creating new content for pages Person/Zef, and Workstream/SBv2
    • This content is also related back to the date 2025-05-04 via the journal page
  • Creating new TODOs assigned to the topic by hierarchy
  • The ability to context switch very quickly between workstreams, meetings, people, without leaving the page.

Now if I go to the page for Zef, I can query for all the TODOs related to that person which looks like this:


If you’ve made it this far, thank you for reading!!!

1 Like

Hello @chipduvet, thank you for the write up!

Wouldn’t the ilinks @zef proposed in the GitHub issue completely address your example 2? Is there any part of it it would not address assuming you always remember to use bullets?

Edit: I see that one thing that would still be missing is breadcrumbs (in LS parlance). The P/Zef > W/SBv2 part which is helpful for context. Anything else?

As for the earlier point you had about hierarchy: for a system like SB that is not an outliner – i.e., does not impose a strict bullet hierarchy, SB seems like it does everything LS does, as long as bullets (aka items or tasks in SB terminology) are involved. Write a bulletless paragraph, and you’re on your own. That has its pros and cons but overall seems reasonable to me.

The biggest downside I see is, one can unwittingly (and easily) orphan non-block content, and never find it again through queries, which in some way makes the note-taking system less trustworthy. But I’m willing to give it try to see whether this is truly a problem in practice. Thoughts?

PS: I can think of some edge cases related to what you brought up, in the current proposal. For example, what happens when I write a paragraph with a link and then the paragraph has bullets underneath it? (@zef: it’d be great to know).

Oh yes, the proposal makes sense. I added the examples for more context.

Fair point, and my mind is definitely stuck in Logseq land.

I hoped to present the Logseq flexibility and workflows to see how the community would solve the same workflows in SB.

Giving it some more thought, as long as we can relate multi-line blocks of content we don’t necessarily need to use a bulleted list (outliner) structure to create the bounds of the relationship. So bullets aren’t really important as much as the ability to easily relate a portion of content.

I think what I’m really after is sort of the inverse concept of SB Transclusions. What I mean is that currently with transclusions, I can target a portion of a foreign page (using a heading) and render that portion of content on my current page. Instead, I would like to “push” content to other pages from my current page. So some way to relate a multi-line block of content with a foreign page or tag, but the content actually lives on the current page (and markdown file).

Let’s say we used a syntax that wraps the multi-line content with triple brackets [[[ {1 or more pages or tags} and ]]]:

[[[ [[My Project]]

# New Topic

Lorem Ipsum

]]]

This content would now be related to the page [[My Project]] and would appear under the Linked Mentions or maybe it could be rendered as a editable transclusion where the content would be live and editable also from the [[My Project]] page.

Any thoughts if this would make sense as a feature?

Hmm, so if I slightly modify your example:

- [[My Project]]
  - # New Topic
    - Lorem Ipsum

then it basically “pushes” this content into the Linked Mentions of [[My Project]] currently (in the latest version). What you’re asking for that’s not currently in the SB feature set:

  • boundary definitions with [[...]] to define the block (an outliner like LS implicitly defines the block to be all children, which SB can’t quite do)
  • live editing of the transclusion (but I see this as a different issue – the fundamental concept of live editing doesn’t exist in SB)

Are these what you’re suggesting?

IIUC, I believe @zef was referring to the idea of “tags” (along with tasks, paragraphs, list items) being a class, not actual tags.

PSA for anyone following along: github 1754 implementing ilinks has been completed, hooray! Thank you a ton, @zef, much appreciated!

This was a crucial piece of the puzzle, especially for anyone coming from Logseq or the likes.

4 Likes

Where is the disagreement? :slight_smile: The way I am seeing it:

  • Dog is the class, or tag declaration in SB jargon.
  • Any object (tasks, paragraph, list item, page) can be made an instance of that class by adding a tag reference, like #Dog, to it.
  • - [name: Fluffy] #Dog here is a list item, which is an object/instance of Dog.
  • [name: Fluffy] is an attribute (OOP naming: instance variable) with name key and "Fluffy" string value added to this class instance.
  • Hence tag feature = create custom object types/classes, instantiate objects from these classes, and provide the ability to query by these object types.
  • A page can be seen as plain Markdown content container and in-app representation of corresponding file in filesystem. Its name can be used for linking via [[My Page]].
  • Pages are objects. Custom object types can be be added to a page via page tags and/or frontmatter.
  • This is in contrast to Logseq, which has pages, blocks, and generic properties. Logseq uses the page concept for linking and file representation, but does not have typed objects like SB.
  • In Logseq, a tag reference actually is the same as a page reference, there a only visual differences.
  • SB hence goes a step further with tags and its object system.
  • This object system potentially allows more features to come in future:
    • tag/class schema validation
    • Crafting personal ontology: we already can create custom objects (instance side), but for now are not able yet to define relations between object types/classes/tags (concept side). E.g. you have a Dog tag and want to express it is a kind of Animal . This could be achieved by making tag pages (like Dog) writable and by adding frontmatter to this tag page.
    • Polymorphic object querying for tags/classes and their sub-types (kinda based on last point): This allows to return all Dogs,Cats,Chickens, … when querying for Animals.

Looking forward to see, what is possible.

Works great, nothing unexpected so far.

Note that this only works on list items, which is as expected. This works:

- [[Cars/Toyota]]
  - [ ] Get the wipers fixed

In this case, [[Cars/Toyota]] will be part of the ilinks for that item, and thus the task will show up in [[Cars/Toyota]]'s Linked Tasks.

This won’t work:

[[Cars/Toyota]]
- [ ] Get the wipers fixed

This is as expected. Just mentioning it here in case it trips up others.

3 Likes

Yes. I used the word “push”, but really the content is just related to both the page it lives on, and any tags or page references included in the first line of the boundary definition.

In the following example, the content block would relate to the Example Page and #exampletag:

[[[ #exampletag [[Example Page]]

MY BLOCK OF ANY MARKDOWN CONTENT

]]]

Yes.

On the first point, the boundary definitions would need a different syntax than double brackets since that’s already used for markdown links. I had proposed triple brackets.

On the second point, yes I agree. It would be a separate feature request.

Just throwing around some thoughts and ideas: on the one hand, I like the idea of a marker to define a block boundary, which makes it explicit and controllable on non-outliner systems like SB (outliners define boundaries clearly).

On the other hand, this is one extra thing I have to worry about getting right. It feels like I’m “programming my notes” rather than dumping them and letting my PKM worry about inferring it automatically.

Hmm, this seems like the price of using a non-outliner PKM. I wonder if there’s a smoother way to infer block boundaries automatically.

I’ve been watching some videos on how people use logseq, the idea of the journal as the main thing, tagging and linking items and then automatically pulling them in dynamically when visiting the linked page. I like this concept and am thinking how to better support it.

Currently linked mentions do not consider context at all. The “snippet” kept is just based on looking x characters back and forward, essentially.

What the “logseq approach” would need is a more structured approach. Indeed since in LogSeq everything is a block, it just makes sense to show the block and its children as a linked mention. Should we make the same assumption in SilverBullet? It would be possible to somehow determine where a link occurs and implement “if link is in a bulleted list item, the snippet is that whole item including its children”. I wonder if that is a sensible default though.

Alternatively this could be a separate thing, for those who want it. Effectively “Linked Outlines”. This widget would query all items with the current page in its links, and then inline those items with its children (I’m about to add an API that will do this). If you use this, you’d likely want to disable the regular linked mentions widget.

Generally I don’t like introducing more “modes” and prefer silver bullet solutions (:wink: ) so perhaps the specialized snippet behavior for items is something for me to try first.

4 Likes

Great! I rely heavily flow you mentioned and it works really well. It minimizes cognitive load while taking notes. While reviewing notes it, it almost magically pulls up exactly what I need, and allows for quick, focused edits.

I think there are a few things that make this very effective:

  • Block hierarchy in transclusions: linked mentions that use block hierarchy to define the displayed block. Your idea above would work great!
  • Sorting: Minor but helpful: sorting the linked mentions by recency. Recency is correlated with relevance. Logseq shows journal pages first (naturally sorted by date), then other pages alphabetically
  • Folding: in LS, linked mentions reflect the original block’s folding state. However, if a block is too large and fully expanded, LS auto‑folds it in linked mentions to keep context scannable
  • Live Edit: this is a big one. Editing linked mentions directly, without having to mentally switch context is incredibly valuable. Live‑editing query results is a major productivity boost for the same reason
  • Hoisting: being able to zoom in on a single bullet point. When clicking on the transcluded block this allows one to instantly get a clean page on just the block and not all the noise surrounding it. Also something I miss quite a bit. I know you mentioned somewhere this would be difficult to do
  • Breadcrumbs In linked mentions: a nice to have feature but honestly just the page reference alone is usually enough context for me 80% of the time
  • Filtering: Logseq allows for powerful and persistent filtering of linked mentions

These are listed in order of importance. The top two or three seem relatively easy to implement, and they were a major reason I decided to try SB.

One additional idea for non‑outliner PKMs: an “outliner mode” that warns when a page contains non‑list blocks. This would help prevent accidental orphaned blocks and maintain trust in the system — something I’ve run into with paragraphs and ilinks as my post above mentions.

Thanks for considering these and for wanting to bring the best ideas into SB!

Sounds great, thanks a lot for having a look at Logseq userbase, too :slight_smile: .

Is it - or would it be - possible to make “Linked Mentions” pager footer somewhat customizable? Like adding a custom page-context query in that footer for each page? If I remember correctly that was a frequent request in Logseq forums, but never had been implemented - as well as other features after shift to db version development.

The main missing feature currently holding me back from transition from Logseq to SB are block references (block-to-block links) - or in more general terms fine-grained linking to page sections and components, not only pages as a whole. I heavily made use of block links all the time in Logseq. Querying backlinks of object types like linked page mentions is great for a top-down overview of note kinds. Block links take a complementary role, in that they enable fine-grained links, like a 1:1 connection between the most atomic note units in the app. In Logseq these are blocks. SB has objects like list item, task, paragraph and others as atomic note units, hence this feature might also be called object linking.

Can we somehow replicate this deep linking in SB with status quo or in future? I’ve read somewhere, you would like to avoid artificial UUID links like ((1234-5678-...)), to adhere to Markdown standard. And I really appreciate this attitude. Maybe we could improve markdown anchor links, like?

  • Rename anchor links as well, if Markdown header is renamed
  • Or even re-introduce markdown anchors to some extent beyond headers only?
  • If so, it would be great to have this feature as first-class citizen: one of block link main advantages is convenient and quick linking. In Logseq you just press ctrl+c on a block, and use ctrl+v in content of another block to create a block link via ((1234-5678-...)). This block link can be aliased and wrapped by markdown link like [renaming that block text](((1234-...))) for better readbility.

There seem to be two dominant PKM styles: journaling (freeform writing) and outlining. My PKM usage is firmly in the outlining camp, and from that perspective the proposed behavior is an obvious win. When a link appears inside a list item, the meaningful context is the entire bullet and its children; not whatever text happens to precede the list. The current snippet model doesn’t serve this use case well.

Perhaps someone else could speak to the journaling side.

As for implementation: offering both snippet modes seems reasonable. The existing snippet logic could stay as-is, with an additional “outline-style context” option for people who work primarily in lists. A sensible default would consider which style most SB users use, and other related settings. A global “Make SB outline friendly” preset that flips several related settings could also help.

A more ambitious approach would be to detect whether the link appears in a list item or in freeform text and choose the snippet style accordingly. That adds complexity and potential edge cases, but it might possibly produce the most intuitive behavior.

This is what I’ve been toying with a bit today. The whole snippet extraction is due for a rewrite — it’s become quite a mess. I’ll see if I can do something nicer that attempts to figure out the right “block” around the link. I’m thinking:

  • For item and tasks: the whole item + its children (as we’ve talked about)
  • For a paragraphs, headers, and maybe everything else: the entire line unless it becomes excessively long.

You can already simply disable it and use your own implementation. Copied and tweaked from here: Widgets

Regarding anchors or UUID or similar things. Yeah, if your model is (like LogSeq) to effectively always assume you have a client running that can hide things (like injected UUIDs) then that’s fine, but I’d really like to keep the markdown SilverBullet produces/relies on clean, even just for portability reasons. I seem to remember that Logseq (or maybe Obsidian as well?) only injects UUIDs when you actually link to a block, but still this will result in messy files which, indeed, I don’t like. I understand their use but…

Correct about Logseq (UUIDs are only injected at the block-level on demand), and yes, the markdown files are very messy, even without UUIDs. Here’s an example of a block:

- Buy stuff
  collapsed:: true
	- DONE [#C] buy soap
	  SCHEDULED: <2024-07-01 Tue>
	  id:: 668847ff-f5a1-48c6-991a-7fc60a4d43a2
	  :LOGBOOK:
	  CLOCK: [2024-05-20 Mon 16:33:05]
	  :END:

which renders as:

- Buy stuff
  -  DONE [#C] buy soap
     SCHEDULED: <2024-07-01 Tue>

Markdown in the Logseq world serves a critical purpose of true data sovereignty, but doesn’t work very well for readability. However, in practice, I’ve used the backend file only occasionally when I suspected data loss, or right now when I’m exporting things.

Seems like a great set of definitions. For items/tasks: a threshold based auto-folding might be something to consider. Personally speaking, I have many blocks that have a dozen subbullets each of which has half a dozen bullets, which would make for a huge entry in Linked Mentions.

Ah, that’s cool!

Yeah, I understand the reasoning. Part of the problem seems to a fundamental limitation with Markdown and Wiki markup languages, that don’t support link anchors in “official” syntax. Then everyone created their own extended flavors, which lead to a total mess of semi standards. Even [[Some page#someheader]]] notation for header anchor links is a specific (but common) wiki flavor, if I am not mistaken. And header names don’t necessarily need to be unique, which potentially leads to ambiguous links at some point in time.

Hm.. header links are possible up to a depth level of 6, so maybe a workaround for now is to use ###### myid for some non-section link. But that’s hacky as well.