Can I find "orphan notes" / notes not in a link roadmap?

(Pat Maddox) #1

I want an agent that finds any notes that are not accessible via “home”… so in this case, it would find “orphan 1” and “orphan 2”. “page 2” is linked to “home” by way of “page 1”.

I hope that makes sense…

(Mark Anderson) #2

Link testing methods (linkedTo(), linkedFrom() and links()) only look at the scope of a single note so I don’t think what you want is possible. Also, the picture shows a map but, to surface a possible unstated assumption, hierarchy created implicit linkage (e.g. parent child). However, I assume you’re talking about Tinderbox basic/text links only - i.e. links you can see (but only if in map view).

I do recall some years back I asked if it would be possible to extend the reach of links() so one could query notes N links away. I recall this lead to consideration of the complexity of this once the number of links scales, e.g. as in the Afghan COIN map:

That map doesn’t untangle. Ironically it has only two inputs (outbound links only) - weather and money - and no discrete outputs (inbound links only). So, I guess resolving link paths might get complex.

I guess this is something to take forward as a feature request.

(eastgate) #3

Here’s one approach that might work.

  1. Define a Boolean attribute $Marked with a default value of false, and a second Boolean attribute $Tested, also with a default of false
  2. Set $Marked(/Home) to true.
  3. Define an agent that looked for notes that have $Tested==false, $Marked==true and $OutboundLinkCount==true
  4. This agent sets $Tested to true, and then sets $Marked to true for every note linked to this note

Eventually, the agent will no longer find any notes; all the marked notes are accessible from /Home.

(Pat Maddox) #4

Hrm I’m not following. I get the basic idea… start with one marked note, and find any notes that link to it. But I’m not getting the action.

So I’ve got an agent with query:

$Marked & $OutboundLinkCount=true

And when I set $Marked=true for ‘Home’ it shows up in the agent.

What’s the action? Here’s what I’ve got:

links(this).outbound.each(x) { $Marked(x) = true; };

My thinking is that it looks at each outbound link and sets $Marked=true… but that’s not working for me.

(Mark Anderson) #5

Agent query:

$Tested==false & $Marked==true & $OutboundLinkCount==true

Note the ‘==’ for equality tests (not legacy ‘=’)

I think there are some problem with your links() syntax.

  • links(this) evaluates the current object which is the alias in the agent. Basic links are intrinsic so may differ between original and alias (although both share the same outbound text links). I suspect links(original) would be more sensible
  • links() has 3 additional mandatory dot-chained parameters that must be given (one may be left blank). See the syntax here. I think you need to leave the link type blank and use $Path as the retrieved attribute value. I think $Path is safer than $Name in case of duplicate $Name values.

Thus, untested, I believe you need an action:

(links(original).outbound..$Path).each(x) { $Marked(x) = true; };

The extra parentheses around links() may be overly cautious but it just helps make pane to Tinderbox’s parser that you want to iterate the list created by links().

With this sort of action, I’d generally do a static test of a sample links(original).outbound..$Path and the then add the .each() before trying it out via an agent. That way you’ve more to go on if the agent fails. for instance 'links(this) might work in a static test but not in the agent , due to the original/alias context issue described above.

(Mark Anderson) #6

I got a moment free to test and this works, for me - based on your map above:

Agent Query:

$Tested==false & $Marked==true & $OutboundLinkCount==true & $AgentQuery==""

The added query term is to filter out agents as (I assume) they aren’t part of the mix. If they are, just omit the last query term.

The action didn’t work until I cached the links output into an actual list-type attribute. The use of parentheses wasn’t enough to pass .each() a valid list of paths. Note we re-set the list once iterated so we don’t save lots of unwanted data in it (especially if there are a lot of links. I used this:

$MyList = links(original).outbound..$Path;

I’ve laid it out to show the discrete expressions within the action. You can safely remove the line breaks and tab-indents if desired.

I wanted to test as a vague memory was that you couldn’t chain onto links() and indeed you can’t - even via parentheses. links() is actually quite an old action code function, preceding dot-operators which is probably why the extra caching step is needed. Perhaps it may be possible at some point that it will all chaining onto parentheses, i.e. as in (links().dir.type.attr).someAction(). But for now, use a list to crystallise the value and don’t overlook the alias-vs-original aspect of TBX basic links.

(Pat Maddox) #7

Nice!! I had tried links.outbound.. but didn’t think to add $Path on there for looking stuff up.

I actually came up with something that I think keeps the $Marked updated, as in it will set it true or false depending on the link configuration:


$Path(original) != "/root"

(that could be anything that excludes stuff that shouldn’t be toggled, which for testing’s sake I just made root)


var myLinks(links(original).inbound..$Path);
$Marked(original) = any(myLinks, $Marked);


With your solution, I can keep a $Tested attribute and know when each note has been tested. To re-test everything, just set all the notes to $Tested=false, $Marked=false. With my solution, the list of $Marked pages stays up-to-date… but there’s no way to know when it’s finished testing.

I think, given the fact that I don’t need this automatically running all the time, and I want to be confident that each note is tested, that I’d go with your approach.

One last question

These solutions don’t account for the difference between note-to-note links, and text links… I’d like it to only consider text links. Is there a way to do that?

(Mark Anderson) #8

Although there are attributes for discrete per-note counts of basic ($PlainLinkCount), text ($TextLinkCount) and web ($WebLinkCount) links, these can’t be unambiguously addressed via action code. The latter’s link-querying tools, e.g. linkedTo(), linkedFrom() and links(), can filter on direction or link type only. I can’t recall the need you describe in this context ever coming up before so that’s probably why you’ve just described what you may want to make into a feature request.

One workaround is to type your links. If the link titles offend your eyes in maps, the the ‘labeled’ option in the Links Inspector.