jkomoros / card-web

The web app behind thecompendium.cards
Apache License 2.0
46 stars 8 forks source link

Allow cards to be related to one another in more nuanced ways #201

Open jkomoros opened 4 years ago

jkomoros commented 4 years ago

To get at the chilling effect of possibly duplicated content, maybe there should be additional relationships between cards.

Right now there's explicit-link/explicit-inbound-link. But another issue (which one?) talks about having a "See Also".

Perhaps there should be different link types: a See-Also, Possible-Duplicate, Subclass-Of (e.g. specific instantiation of broader principle card), Superclass-Of

Superclass/Subclass should be an automatic relationship, akin to explicit-link/implicit-link

Sibling is a non-overlapping relationship to multiple items that are are all children of the same parent. (Is this automatic? It's possible to imagine multiple different distinct sets of sibling for the same parent... although I guess that's a Smell that there should be distinct sub-children cards then)

Overlaps-with is a relationship where the content might be substantially duplicated; where they aren't NECESSARILY distinct siblings that will always exist.

Overlaps-with is a reciprical relationship. Ideally they'd create an auto-clique where all of them are known to be overlapping with one another, as a candidate for consolidation/being subsumed by the parent one. Maybe even an auto-created card that combines the differente potential dupes into one index card? The overlaps sets can't just be a single one that each card is a member of; you can imagine a single card being parts of two overlap-sets. So maybe it's a thing where you create the equivalence-class card, and then mark it a superclass of the possible-duplicates. And then maybe you have a way to mark the equivalence-class card as a possible subsumption target.

You need some way to mark cards as being potential targets for subsumption. If you think you have cards that are subsumption targets you parent them to the equivalence-class card and mark them with a boolean saying they're not long for this world or are a candidate for subsumption.

You can think of explicit link/inbound-link as a tree that doesn't necessarily establish a hierarchical parent-of/child-of relationship of the concepts.

These non-explicit links should be rendered in a widget on the card itself (or in the info panel) automatically. We should go through an update content that manually does the "see also" and "subclass of" pattern in the body fo the card. In fact, you can view this feature as in some way paving a cowpath.

Originally captured in #200

See also #145.

jkomoros commented 4 years ago

These relations are a form of directed graph. For each type of linkage there's a parent side and a child side. There is no such thing as an undirected edge; for that, link to an implied parent that is unpublished.

When rendering a link for a parent relation, if the immediate parent is unpublished, walk up the chain and render the first one that shows it.

When rendering children of a given card, it can optionally hoist children that are part of anonymous/unpublished children as though they are on the card itself.

There are different types of directed relationships, with different semantics and properties.

One is link/inbound-link. Another is example-of/principle-of. Another is superclass/subclass.

Think of each directed relationship type as a different graph with different semantics. Some graphs are enforced to be DAGs (inbound-link/link is not because it is implied by the card-link structure). Some only allow one parent of that relation type.

The links are encoded in a card.relations.TYPE.LIST structure. The program configures the inverse relationship types in code, e.g. link/inbound-link.

Are the inverse relationships enforced via the cloud function or live? (The latter is harder when there are different editing ACLs for different authors in the future)

jkomoros commented 4 years ago

What types of relations are there, actually?

The ones like equivalence class will be a bit annoying in practice because you'll have to create a stub-card to hold the relation, although we can make that as easy as we make the "select a card to link to in card-link" right now and use the same flow

A number of the relations are conceptually subclasses or instantiations of others. Ideally there'd be a way for example to get all instantiation-of/abstraction-of that also includes example-of/principle-of as well.

Since there's a see-also --> equivalence-class-parent, and a possible-duplicate-->possible-duplication-parent, it would be nice to have a thing that takes an existing see-also relationship and when you add another one (or add a reciprocal) offer to instead remove the original see-also and replace it with the equivalence class, allowing you to select the parent card (or create an unpublished stub)

Types of relationships (note, some are accidentaly reversed)

* child/parent
  * link/link-inbound
  * subclass/superclass
  * instantiation/abstraction
    * example / example-of
  * category-instance / category
    - see-also / see-also-inbound
  * equivalence-class-child / equivalence-class
    - overlaps-with / overlaps-with-inbound
  * possible-duplicate-child / possible-duplicate-set
    - posssible-duplicate-of / possible-duplicate

There are straightforward sub-class relationships, and then there are individual/clique versions (the individual version is denoted as a dash below the *)

jkomoros commented 4 years ago

The various relations should be able to be output on the card itself, perhaps in the footer. Different types of relations would show in different ways.

jkomoros commented 4 years ago

On looking at this closer it's not clear that it's worth it to have different parent/child link types be subclasses. There's only one example of that in the hierarchy, and even that is debatable. It's probably just as easy to have parent/child be the generic "any parent relationship", and then have the only special type of links be the direct or indirect version of equivalence classes and the like.

The links should be stored in map objects on the card object. You can use arrayUnion and arrayRemove on lists inside a map: https://stackoverflow.com/questions/54190855/is-it-possible-to-update-an-array-in-a-map-by-using-arrayunion

Currently inboundLinks are updated on the server in a cloud function. That makes the local transactions faster and easier to do, but note that means that there would need to be two different maps: one for direct connections and one for the inverse connections, otherwise it would be easy for the cloud function would never stop operating.

Ideally you'd be able to add a link from parent to child, or also from child to parent, and have the other one added automatically.

When calculating descendants, ancestors, publishedParent, publishedChildren, of a given card for a connection type, ideally we cache the results of the graph traversal. A simple thing would be to blow away the cache whenever cards changes; later we'd want to only blow away the specific items in the cache when cards involved directly in its graph position change.

Are there any connectionTypes that should actually have singleParent? If not, just leave that complication out.

We'd configure the connection-types in a similar way to CARD_FILTER_CONFIGS in reducers/collection.js. That is, have one config block and then different config constants derived from it.

//In util.js

const CONNECTION_TYPES = {
 //parent -> child, child -> parent, singleParent
  'links' : ['outbound_links', 'inbound_links', false]
  'example' ['example', 'example-of', false]
}

//For all of these methods, connectionType, may be `GENERIC` constant to mean "any type of parent connection". Each constant type has a parent->child direction name and a  child->parent name. You never pass those names, and instead pass the generic name for both. e.g. instead of passing 'inbound_links', you'd pass 'links'

//given e.g. 'links', returns e.g. 'outbound_links'
parentToChildConnectionType(genericConnectionType)
childToParentConnectionType(genericConnectionType)
//Given e.g. 'inbound_links', returns 'links'
genericConnectionType(parentOrChildConnectionType)

//Simply returns cards, but removing any items that aren't published. Typically you filter out unpublished if you don't want those for any of these.
onlyPublished(allCards, cards)

//Returns DIRECT children, which might be unpublished
cardChildren(allCards, card, connectionType)

//Returns DIRECT parents, which might be unpublished. If hte connectionType is one that has singleParent = true, then there will be 0 or 1 parent, otherwise it might be 0 to n.
cardParents(allCards, card, connectionType)

//cardPublishedChildren is like cardChildren, but it will walk through any number of unpublished cards who are children until it finds a depth that has cards who are published, and returns that set. All cards returned will be published.
cardPublishedChildren(allCards, card, connectionType)
cardPublishedParents(allCards, card, connectionType)

//Will return ALL cards that are downward from this  connectionType, published and unpublished
cardDescendants(allCards, card, connectionType)
cardAncestors(allCards,card,connectionType)

//Will go up to the parent of that connectionType, then return all children who are not the given card downward. Does not operate on multiple sibling levels, just the one. Cards returned will be published or unpublished.
cardSiblings(allCards, card, connectionType)

TODO: think through how it works to add a connection to a card, including the UI, how you would create an indirect/glue card, and how the data.js modifiers are done to nesure the inverse connections are set up

jkomoros commented 4 years ago

Instead of having there be a multi- and singular version of e.g. possible_duplicates, just make it really easy to create unpublished, placeholder cards that are: orphaned, have a 'published: false' todo_override. Maybe these should be of card type 'glue' or something like that, and then when saving ensure that any cards of type glue are unpublished and no published auto-TODO and are orphaned.

Do we also need a redirect card type? Is that in this issue or another one? That allows you to unpublish cards, nah just do it in #146; the connections here will just make it easier to add refactor buttons for cards that are related to each other via these connections.

There needs to be a way for admins to see the indirect cards

In a perfect world we'd also allow URL filters like /connections/CARD_ID/CONNECTION_TYPE/RELATIONSHIP_TYPE, so e.g. /c/connections/insights-are-cool/links/descendants/. The connections would be a special thing in the URL to parse, and would have to be first, or right after the set name, and then after it you could have the normal sorting and filtering. This would be cool, but hard to do because you'd need to create a bespoke filter on the fly (all of the other ones are prebaked). ... But how are date ranges done? (I guess I never actually did those)

jkomoros commented 4 years ago

And the card-links in the body of the card should have a link-type attribute, which denotes that the given link is of the given type. And we should keep track of the fact it's an auto-link in the data model, so we don't render additional manifestations on it