Tinderbox Forum

Linking confusion

Hi, I’m trying to set up a workflow for citation analysis. Each of my reference notes has an attribute $citekey like “2000Smith23” and I want to be able to write @@2000Smith23 in the text of another note and have an agent automatically handle adding and removing links between the notes.

I’m new to tinderbox but after a frustrating series of experiments I got something sort of working but I don’t get it. I have an action with the Query: inside("/home") & hasLocalValue("citekey") and the Action:

var $myref($citekey); 
linkFromOriginal(find(inside("/home")&$Text.contains("@@".$myref)), "cite");

But now I have a bunch of questions. Within the contains() function, why use a “.”? The following expressions which I got from the help files or online don’t seem to work: “@@”+$myref (links all notes that have an “@@”) "@@"+"$myref" (no links created at all).

Using $citekey(this) instead of having to define $myref doesn’t work either.

Using unlinkFrom instead of linkFromOriginal doesn’t unlink the links that were created

unlinkFromOriginal doesn’t auto complete in the action box, does this command still exist?

And this:

var $myref("@@"+$citekey); 
linkFromOriginal(find(inside("/home")&$Text.contains($myref)), "cite"); 

seems to link just about every note to every other one including self loops.

What am I missing?

What you’re doing is probably one of the more complex things you can do in a Tinderbox action (even if unwittingly) because:

  • Agents act on aliases, though luckily we now have linkFromOriginal which eases part of the issue
  • In a find() the ‘this’ designator has a different meaning. To use a value from the note/alias being processed within the find() query, you need to use the ‘that’ designator.
  • Unlike an agent query a find() query doesn’t de-dupe. Instead it returns the paths of the original and/or all aliases in query scope, so if trying to link to a single instance of the target (be it an alias or an original) you need to specify that particular object otherwise the link/unlink action will create/remove multiple links.

As to this code:


The ‘.’ in a regex pattern means any character except a new line. But because it’s not within the regex pattern (contain() takes a quoted regex, it’s anyone’s guess as to how Tinderbox will parse this - i.e. the outcome can’t be predicted.

Furthermore, I’m now sure you’ve correctly understood how var() works:

var $myref($citekey)

I don’t think a var variable name can use a $prefix nor should it (case-sensitively) match the name of an existing reference (see the link above to documentation of the var() function). Don’t assume the $ prefix is the same of [some computer language] but rather read tutorial PDFs in the apps Help menu to understand how Tinderbox’s action code works.

I don’t have enough info to properly replicate your document but I think your whole first code example should be replaced with this:

linkFromOriginal(find(inside("/home")&$IsAlias==false&$Text.contains("@@"+$citekey(that))), “cite”);

Separately, depending on the number of child notes in ‘home’ I’m not sure how well this agent-approach scales. Unless every child** of ‘home’ is a target for linking, I’d consider using a common prototype (or limited set of prototypes) that apply only to the target group of notes. Also, given the action runs a find() query using a regex operator like .contains(), I’d consider moving the code into an edict run in the reference notes (doing so via a prototype).

** note that inside() tests only children of the target container, and not those children’s descendants.

If still stuck, could you consider posting a link to a small TBX that demonstrates your problem space so we’ve a more complete common reference from which to help you.

HTH :grinning:

Awesome, than you!

I hadn’t spotted the lack of $ prefix on local vars, and also hadn’t realised there was a bunch of $myXXX attributes already defined. Who knows what it was resolving with my code. After fixing those two things it at least works as expected now, with ‘+’ for concat.

this and that: spot on, though I’ve gone with a var as I want to also unlink. I couldn’t find the that designator in the built in help, is atbref7 considered more complete?

I’ve not thought too much about scaling at the moment, just want to get something working before optimising. My understanding is that linkFromOriginal won’t link again if it already exists so I just threw everything at it without filtering. Performance wise, is it better to pre filter the duplicates and aliases or let the check-for-existijng-link code do it?

Is there a way to manually run the Edicts? I’d like it to responsively create the links as I’m working but there will be long periods where nothing will change that affects linking. Waiting an hour for the edicts to run would drive me crazy I think.

Now I have this:

var tmpref("@@"+$citekey); 
linkFromOriginal(find(inside("/home")&$Text.contains(tmpref)), "cite");
unlinkFromOriginal(find(inside("/home")&!$Text.contains(tmpref)), "cite");

which is probably not as efficient as using an each() and an if else but I want to get something working first. The unlinking part doesn’t unlink. Any ideas?

@alexei – OT: how do you markup text in a code block in Discourse to colorize that text red?

@PaulWalters, I just used triple back-tics fencing (took a guess from markdown):



The various linkTo/From linking operators won’t duplicate links but note:

  • Links of different link type are discrete links in the context, as is an untitled link (i.e. has no link type). As you mention a link type ‘cite’, the above operators should ensure at least one link of type existing between the source-target in context.
  • Aliases always share their original’s text links but may have their own basic links that differ from the originals. Put another way, an alias inherits its original’s outbound basic links until it has one or more outbound basic links of its own. Inbound basic links aren’t inherited (as they point to the original) but it is possible to link to an alias.

Sorry if that sounds complex, but Tinderbox is a toolbox for notes and its tools have to accommodate disparate and occasionally conflicting use cases.

Yes, see here, last bullet point.

Hard to say without seeing the file. But, I’d check - given the points above - that the link you’re trying to remove is being correctly targeted.

The normal procedure at this point is to back the code off into a small test file. First check unlink works on a simple test and then add back the variations of your file to see where/if things start to not work as expected.

Ah, I see

var tempref("@@")


var tempref("@@")

For reference I’ve got a working solution.

I have a bunch of notes with prototype Reference that have a citekey attribute with a unique id ([year][author][page]), and I wanted to have notes automatically linked whenever @@id appears in the text of other notes. This is all to make unravelling citations in papers easier. I can write notes on the paper I’m reading and put in the corresponding citations with ids I can generate from the references.

Then I have two code notes:


python -c "import sys, re; print(';'.join(set(re.findall('@@(\w+)', sys.stdin.read()))))"

This passes the $Text to a python script which picks out all the ids the match the pattern @@something.


var result;

unlinkTo((links.outbound."cite".$Path), "cite");

$MyString="Not found:";

result = runCommand($Text(FindCitesPython),$Text);

result.each(x) {
	$MyList = find(IsAlias==false&$citekey==x);

	if ( !$MyList.empty ) {
		linkToOriginal($MyList.at(0), "cite");
	} else {
		$MyString =$MyString +" "+x;

This removes all the cite links, grabs the links in the $Text via python, and looks for matching notes to link to. The attribute $MyString gets populated with the ids that failed to match, and for which I need to hunt down the references to add to tinderbox.

Finally a stamp with


runs the whole thing. The stamp means performance is not really and issue as the linking runs manually and I can be selective about what to run it on and when.


@alexei – thank you :raised_hands:

Not only very useful, of course, but a tour de force showing how to use several Tinderbox skills. Great job.

Interesting. Good point about the fact you’re running this on demand rather than for a rule as runCommmand access to the shell wasn’t intended for always-on tasks; run as when needed, this is a useful approach to extending available functions. Thanks.