Put the dual-bay sibling in the triage band, and fix the gage counterpart

The machine page opened with a dual-bay banner directly above a hero band saying
'Controlled by'. Both answer 'what am I looking at', so the sibling is now a chip
in the band. The band takes a generic extraitems prop rather than learning what
dualpath is - the pairing is resolved by the machines plugin, not from the
relationships endpoint.

MeasuringToolDetail filtered for a machine counterpart, but the import creates a
controls edge PC -> tool, so a gage's counterpart is a computer. It rendered
nothing on every metrology station that has a PC.

Verified on screen. Full suite 2178 passed, frontend 280 passed.
This commit is contained in:
2026-08-29 19:38:07 -04:00
parent 8666e0271c
commit dd7dd435c0
3 changed files with 57 additions and 32 deletions

View File

@@ -19,6 +19,20 @@
</span>
</div>
<div v-for="item in extraitems" :key="item.label" class="triage-item">
<span class="triage-label">{{ item.label }}</span>
<span class="triage-values">
<component
v-for="link in item.links"
:key="link.text"
:is="link.to ? 'router-link' : 'span'"
:to="link.to || undefined"
class="triage-chip"
:class="{ link: Boolean(link.to) }"
>{{ link.text }}</component>
</span>
</div>
<div v-if="accessmethods && accessmethods.length" class="triage-item">
<span class="triage-label">Remote access</span>
<span class="triage-values">
@@ -86,7 +100,15 @@ const props = defineProps({
// printer through usesprinter and defaultprinter. Hardcoding 'controls'
// left the band blank on the printer page, which is the one page where the
// link is not obvious from the asset itself.
relationshiptypes: { type: Array, default: () => ['controls'] }
relationshiptypes: { type: Array, default: () => ['controls'] },
// Triage facts a page knows but core cannot derive from a relationship
// edge. The machine page's dual-bay sibling is the case this exists for:
// it is resolved by the machines plugin, not from the relationships
// endpoint, and it used to sit in its own banner above the hero - two
// strips of chrome saying two halves of the same "what am I looking at".
// Shape: [{ label, links: [{ text, to }] }]. A link with no `to` renders
// as plain text rather than a dead chip.
extraitems: { type: Array, default: () => [] }
})
const machines = ref([])
@@ -102,6 +124,7 @@ const hasAnything = computed(() =>
Boolean(slots.actions) ||
Boolean(props.liveness) ||
machines.value.length > 0 ||
props.extraitems.length > 0 ||
(props.accessmethods && props.accessmethods.length > 0) ||
Boolean(props.locationname)
)

View File

@@ -20,16 +20,6 @@
<div v-if="loading" class="loading">Loading...</div>
<template v-else-if="machine">
<!-- Dual-bay sibling banner: a Dualpath pair is one physical machine -->
<div v-if="machine.dualpathpartner" class="dualpath-banner">
Dual-bay machine - sibling bay:
<router-link
v-if="machine.dualpathpartner.machineid"
:to="`/machines/${machine.dualpathpartner.machineid}`"
>{{ machine.dualpathpartner.assetnumber }}</router-link>
<span v-else>{{ machine.dualpathpartner.assetnumber }}</span>
</div>
<!-- Hero Section -->
<div class="hero-card">
<div class="hero-image" v-if="machine.machine?.imageurl">
@@ -80,6 +70,7 @@
:assetid="machine.assetid"
counterparttype="computer"
counterpartlabel="Controlled by"
:extraitems="triageextras"
/>
</div>
</div>
@@ -259,7 +250,7 @@
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { ref, computed, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { machinesApi } from '@/api'
import { withBase } from '@/utils/basePath'
@@ -281,6 +272,25 @@ const { isEnabled } = useIdentifierFlags()
const loading = ref(true)
const machine = ref(null)
// The dual-bay sibling, as a triage chip. It had its own banner above the hero
// until the band existed; two strips of chrome answering two halves of the same
// "what am I looking at" read as clutter, and the sibling belongs beside the
// PC that drives the machine rather than above it.
const triageextras = computed(() => {
const partner = machine.value?.dualpathpartner
if (!partner) return []
return [{
// Says what the pair MEANS, not just that a link exists: a dualpath pair is
// one physical machine in two bays, which is why the old banner spelled it
// out rather than showing a bare number.
label: 'Dual-bay sibling',
links: [{
text: partner.assetnumber,
to: partner.machineid ? `/machines/${partner.machineid}` : null
}]
}]
})
const { heroWarranty, warrantyDate } = useWarrantyBadge(() => machine.value?.assetid)
// relationships render via the shared AssetRelationships card
@@ -334,21 +344,6 @@ async function confirmRetire() {
font-family: 'SF Mono', 'Monaco', 'Consolas', monospace;
}
.dualpath-banner {
margin-bottom: 16px;
padding: 10px 14px;
border-radius: 6px;
background: var(--bg-card);
border: 1px solid var(--border);
border-left: 4px solid var(--primary);
color: var(--text);
font-size: 0.9rem;
}
.dualpath-banner a {
color: var(--link);
font-weight: 600;
}
.feature-tag {
display: inline-block;

View File

@@ -47,13 +47,20 @@
</div>
</div>
<!-- The 6am band. A gage usually has no controls edge at all,
and the band renders nothing when it has nothing to say - it is
here so the five asset pages stay one shape rather than five. -->
<!-- The 6am band: which PC drives this instrument.
A metrology tool is reached through the PC that runs it - the
import synthesizes one measuring-tool asset per metrology PC and
a 'controls' edge PC -> tool (stage_metrology), so the
counterpart here is a COMPUTER, exactly as on the machine page.
This was first written as counterparttype="machine" labelled
"Used on", which filtered for a counterpart that cannot exist
and so rendered nothing on every gage that had a PC. A hand-held
gage genuinely has no such edge, and the band correctly shows
nothing for it. -->
<AssetTriageBand
:assetid="tool.assetid"
counterparttype="machine"
counterpartlabel="Used on"
counterparttype="computer"
counterpartlabel="Controlled by"
/>
</div>
</div>