Early last month a client approached me just as I was getting ready to put together the final piece of Intertwingler, my flagship project of 2023. He wanted me to carve off a piece of one of Intertwingler's predecessors as a standalone app—a thing called App::IBIS, that concerned the construction of concept schemes. This was something I had haphazardly grafted onto the main IBIS tool, itself a ragged proof of concept I had knocked together over the span of a couple weeks, over a decade ago.

To quote one Maciej Cegłowski: We do these things not because they are easy, but because we thought they would be easy. I had initially believed I could just spend half a day carving off the SKOS bit or otherwise introducing a configuration parameter that would hide the IBIS part. This would have been a fine strategy as I don't really care about what happens to this software at this point, since a shippable Intertwingler will replace it.

The proximate problem with the SKOS interface is that it never really was fit for purpose. Moreover, it was sufficiently different from its IBIS counterpart that the front-end code couldn't be adapted without a rewrite. Said front-end code was written on top of JQuery, because I wrote it ten years ago, prior to certain guarantees of cross-browser compatibility that exist today. So now I'm looking at rewriting the JavaScript for the whole thing. But, the UI for the IBIS part sucks too, so if I'm going to have to rewrite a bunch of JavaScript, I'd rather do it for a less-bad UI.

What made the UI suck in particular was this fixed region at the bottom of the screen reserved for data entry. After a decade of using this tool (after all, it was surprisingly useful despite being so rough), I found this paradigm terrifically cumbersome. It boiled down to having to click around the screen a bunch every time you wanted to add a thing, which was antithetical to my original goal of making data entry quick. There was a set of checkboxes for one or more types of semantic relation (which actually didn't make much sense in practice), an overzealous OS-supplied autocomplete dutifully offering you stale or redundant entries, and an unwieldy dropdown for connecting existing items, which you had to explicitly toggle into view. The SKOS side was a little simpler, due to there being only one type of entity and three kinds of semantic relation (versus three and twelve for IBIS), though I did not feel good about putting lipstick on that pig.

So if I was going to put effort into fixing the SKOS UI, I wanted it to be a decent UI. If I was fixing the SKOS UI, it would implicitly fix the IBIS UI because they're basically the same thing. To fix the UI, I would have to fix the markup, and to fix the markup, I would have to fix the server-side code that generates the markup. And if I'm going to do that, then I'm going to need to fix the way the markup is generated, because I will no doubt have to go back and tweak it several times.

I started by cleaving out the code that generated the pages for type-specific entities and putting them into their own modules. There are only two of these, and they were a little over 300 lines (of Perl—this is an antique, after all) apiece, plus a 450-line common ancestor. All these 1100-some lines of code do, more or less, is belch out some markup—and very similar markup at that. I wrote this tool initially around the time I was experimenting with bucking the MVC paradigm, but had yet to perfect the alternative (which I wouldn't do until about five years later, in 2018 or so). What this meant was that there was too much detail happening in the markup generation, which made it cumbersome to change iteratively. As such, I ultimately decided to blow all of this away and create a universal stub function with no real structure to speak of, just a page with a list of literal data members and links to adjacent resources, along with embedded semantic metadata.

The IBIS tool can be understood as a special-case precursor to the generic piece of infrastructure that is Intertwingler. As I have mentioned many times in past missives, the IBIS tool is only incidentally useful. I wrote it for the express purpose of testing a protocol I designed, to radically simplify the development of RDF-based Web applications by embedding graph manipulation operations directly into ordinary HTML form fields. That turned out to be easy. What turned out to be hard—and ultimately took another decade, mostly not working on the problem—was displaying that information back. This is because dispatching the right layout depends on a standard inferencing mechanism that has only been implemented in a handful of programming languages (not the one I was using) and would either demand a huge sunk cost to either write the missing piece (into a language that was past its prime), or rewrite all the other pieces into one of the languages that had it (which is what I ultimately did).

This is ultimately why the IBIS tool was a dead end: I had to fake the inferencing code by writing out a bunch of mapping tables by hand. If I wanted to add another kind of entity or relation, this would explode combinatorially in the amount of mappings I would have to write. It was totally unsustainable, but if we can accept that the IBIS tool has all but served its purpose, we can retrofit it for one final tour before retirement.

So that's what I did: I wrote up a few key routines that superficially resembled analogues in Intertwingler, and created something of a cleavage plane that would enable all subsequent work to be portable (or at least near-portable) between the IBIS tool and Intertwingler.

The upgraded interface itself is actually a lot simpler than what it replaces. I had designed the original data entry interface with a JavaScript-free fallback but very quickly abandoned that as too quixotic. The replacement is actually something I dreamed up a long time ago but never implemented, again because prior to Intertwingler, it would have been a dead end.

The original data entry UI was focused on adjacent nodes, as I had anticipated one would be connecting them together by more than one semantic relation at once, so I wanted to bundle that part together. From experience, it turns out that this doesn't actually happen all that often, so I unbundled the data entry UI to the parts of the screen that represent the semantic relations themselves.

The next significant change was to get rid of the explicit toggle between create new and connect existing entities, which alternated between a text input and a (potentially gigantic) dropdown. I replaced it, provisionally, with the <datalist> construct that didn't exist when I originally wrote the tool. This meant that I only needed a single text input that would populate its value from the list if it matched, or otherwise afford the entry of a new entity.

That was surprisingly it for the UI upgrade. The RDF-KV protocol did its job and made it embarrassingly easy to communicate with the server. I did have to rewrite the Sass, because it was easier than trying to decipher what was already there, but that wasn't that big a deal. There were a couple other entailments too, namely around the fact that I invoked my bastard trademark of using browser-side XSLT to transform (X)HTML into itself. What I like about it—and why I've been a resolute user of XSLT since I first picked it up in 2001, is that it is a high-performing, platform-agnostic, standard, declarative language that only operates over the information you give it, and it is incapable of outputting a syntax error. I extended this bastard toolkit, in 2016 and 2018 respectively, with a module for querying embedded RDFa, and another for seamlessly transcluding page components. I like working this way, because it helps define what I need to make on the server side to feed into the template, and what kinds of entities and relations need to be represented in the system.

To reiterate, what the client wanted was a way to operate over just SKOS concepts. Even though at this point I had a UI that wasn't a total disgrace, I still needed to figure out an approach to deliver this that wasn't totally stupid. In the original cut of the tool, there is an implicit resource at the root address that is generated, and not part of the underlying graph. This happened to be an entity of class ibis:Network (which, incidentally, is a subclass of skos:ConceptScheme), a fact that was hard-coded into the markup-generating function for that location (and nowhere else). This, in addition to being a total hack that tied the address of the installation site to a particular object—and by extension, a particular representation of that object—it also wouldn't resolve in graph queries. This had to get resolved if I was going to give my client what he wanted.

Here I get into a situation that I'm still not completely sure how to navigate, because until Intertwingler, I didn't have a substrate through which I could move quickly enough for feedback to be meaningful, so I have yet to establish a set of best practices. You can think of the IBIS tool as an early attempt on my part to marry REST (as Fielding originally conceived it) to RDF. What this implies is that every page is more than just a page, it's also a structured data object. Per The Hypermedia Constraint (erstwhile Hypermedia as the Engine of Application State or HATEOAS), the current representational state—that is, whatever is at the location displayed at the top of your browser—contains instructions for accessing subsequent states. The role RDF plays is in providing an extensible mechanism for expressing what those subsequent states mean. So the question you ask yourself when making an app or website in this style is less what pages do we need? and more what primitives do we need?

The mechanism that puts together the presentation the user ultimately sees, has all the information it needs to perform complex logic and fetch additional resources. If the resource in context is an instance of a particular class, then we can expect it to have certain members, whose values will themselves be instances of certain classes, which will imply certain members, and so on. Practical applications of this state of affairs include dispatching appropriate visual designs, conditional processing of interface elements, and the all-important reuse of content.

The problem that I alluded to when you're trying to come up with primitives is you get lacunae: gaps of uncertainty between parts you're otherwise reasonably confident about. In RDF, classes and properties are registered in schemas and/or ontologies, a hair-splitting distinction smoothed over by the term vocabulary. Like anything else in the world of software, you either find these artifacts, or you make them. My own tendency is, if I can't find a a vocabulary in time that a) contains a useful-looking class or property and b) doesn't look abandoned, I rough in a provisional one with the entities I need, and gradually supplant them with more established ones if they start to look redundant. In this case, I made a Collaborative Graph Tool Ontology (which I had actually started a year ago), intended to encode only the largely-presentational metadata associated with the tool itself, separate from its instance data.

The strategy here was to change the root address of the tool to an instance of cgto:Space, which would have a special functional property called cgto:focus which would identify the main thing you were working on. (This relation, of course, can be overwritten at will.) Through this mechanism I can say: if the focus is a skos:ConceptScheme, only show the UI for SKOS; if it's an ibis:Network, then show the IBIS UI which extends it.

Here is where I think I may have made a mistake, which I attribute to travelling over the holidays, so what may have been reasoned out in a day or two got smeared out over two weeks. For some reason my brain insisted that an ibis:Network had to have a separate skos:ConceptScheme, so if a concept scheme was in focus, there needed to be a way to unambiguously resolve the canonical IBIS network. I created a property ibis:concepts and functional subproperty ibis:main-concepts to articulate this, but even this was insufficient because there could still be more than one ibis:Network that nominated that particular skos:ConceptScheme as its main concepts. After wrestling with this for several days, it occurred to me finally that it was totally legal for a concept to belong to more than one concept scheme, and since ibis:Network is a subclass of skos:ConceptScheme, it would be sensible enough to add an upgrade button to your concept scheme in focus, which would turn it into an ibis:Network, and reveal the IBIS UI.