feat: cross-link related blueprints on the detail page

The title-block "Extends" now links to the parent blueprint when it is
illustrated, and a new "Related blueprints" section lists hosts, siblings,
and extensions. References resolve to live links via the registry and fall
back to plain text otherwise, so PasswordAuth and RememberMe now interlink.
This commit is contained in:
Julien Calixte
2026-07-02 23:36:15 +02:00
parent 4d2db834ac
commit 1ba85e3235
2 changed files with 81 additions and 1 deletions

View File

@@ -1,12 +1,15 @@
<script setup lang="ts">
import { computed, ref, watch, type Component } from "vue"
import type { Blueprint } from "@/data/blueprint"
import { slugForName } from "@/data/blueprints"
import FunctionReadout from "@/components/FunctionReadout.vue"
import CompositionMap from "@/components/CompositionMap.vue"
import StateMachine from "@/components/StateMachine.vue"
const props = defineProps<{ blueprint: Blueprint; specimen: Component }>()
const extendsSlug = computed(() => slugForName(props.blueprint.extendsName))
const activeId = ref("full")
// reset to the overview whenever we switch blueprints
watch(
@@ -47,7 +50,12 @@ const funcCount = computed(() => props.blueprint.functions.length)
</div>
<div class="tb-cell">
<div class="tb-k">Extends</div>
<div class="tb-v">{{ blueprint.extendsName ?? "—" }}</div>
<div class="tb-v">
<RouterLink v-if="extendsSlug" :to="`/b/${extendsSlug}`" class="xlink">{{
blueprint.extendsName
}}</RouterLink>
<template v-else>{{ blueprint.extendsName ?? "" }}</template>
</div>
</div>
<div class="tb-cell">
<div class="tb-k">Role</div>
@@ -168,6 +176,23 @@ const funcCount = computed(() => props.blueprint.functions.length)
</div>
</template>
<!-- COMPANION 3: related blueprints -->
<template v-if="blueprint.related?.length">
<h2 class="section">Related blueprints</h2>
<div class="related">
<template v-for="r in blueprint.related" :key="r.name">
<RouterLink v-if="slugForName(r.name)" :to="`/b/${slugForName(r.name)}`" class="rel link">
<span class="rel-name">{{ r.name }} <span class="rel-arrow"></span></span>
<span class="rel-rel">{{ r.relation }}</span>
</RouterLink>
<div v-else class="rel">
<span class="rel-name">{{ r.name }}</span>
<span class="rel-rel">{{ r.relation }}</span>
</div>
</template>
</div>
</template>
<footer class="credit">
<div>
<span class="ck">SOURCE</span> blueprint-ontology / blueprints / {{ blueprint.slug }} ·
@@ -456,6 +481,54 @@ code {
color: #7ea6cd;
}
/* cross-links */
.xlink {
color: var(--color-secondary);
text-decoration: none;
border-bottom: 1px dashed color-mix(in srgb, var(--color-secondary) 50%, transparent);
}
.xlink:hover {
border-bottom-style: solid;
}
.related {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 8px;
}
.rel {
display: flex;
flex-direction: column;
gap: 2px;
border: 1px solid rgba(200, 226, 255, 0.14);
padding: 8px 12px;
text-decoration: none;
}
.rel.link {
border-color: rgba(200, 226, 255, 0.3);
cursor: pointer;
transition: all 0.12s;
}
.rel.link:hover {
border-color: var(--color-secondary);
background: var(--color-base-200);
}
.rel-name {
font-size: 13px;
color: #7ea6cd;
}
.rel.link .rel-name {
color: var(--color-secondary);
}
.rel-arrow {
font-size: 10px;
}
.rel-rel {
font-size: 10px;
letter-spacing: 0.1em;
text-transform: uppercase;
color: #4f7099;
}
.credit {
margin-top: 34px;
padding-top: 14px;

View File

@@ -302,4 +302,11 @@ export const rememberMe: Blueprint = {
},
stateMachine: REMEMBER_ME_MACHINE,
related: [
{ name: "PasswordAuth", relation: "host · requires" },
{ name: "StatelessSession", relation: "sibling capability" },
{ name: "MFA", relation: "sibling capability" },
{ name: "Captcha", relation: "sibling capability" },
],
}