From 4cd8c921f9f3dc3e855199edb25321eb3ee90baa Mon Sep 17 00:00:00 2001 From: "Maksym Hryzodub [DREAM]" Date: Wed, 16 Sep 2026 16:07:02 +0300 Subject: [PATCH 1/4] feat(admin): expand peer card inline under the clicked agent (CLEAN-94) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Add peer preview rendered below the whole candidate list — under the fold with ten agents, so selecting looked like nothing happened. Each row is an accordion now: the card and Connect button open right under the click, and an Info pill with a chevron says the row is clickable and which one is open. Co-Authored-By: Claude Fable 5 --- .../agent/peer/components/peer/Picker.vue | 84 ++++++++++++------- 1 file changed, 56 insertions(+), 28 deletions(-) diff --git a/admin/slices/agent/peer/components/peer/Picker.vue b/admin/slices/agent/peer/components/peer/Picker.vue index e18068cc..72330d00 100644 --- a/admin/slices/agent/peer/components/peer/Picker.vue +++ b/admin/slices/agent/peer/components/peer/Picker.vue @@ -7,7 +7,7 @@ import { DialogRoot, DialogTitle, } from 'reka-ui'; -import { IconCheck, IconSearch } from '@tabler/icons-vue'; +import { IconCheck, IconChevronDown, IconSearch } from '@tabler/icons-vue'; import { toast } from 'vue-sonner'; import { AGENT_STATUS_VARIANT } from '#agent/utils/agentFormat'; import type { AgentStatusTypes } from '#agent/domain'; @@ -18,12 +18,16 @@ import { } from '#peer/stores/peer'; /** - * Pick an agent, read its card, then connect (CLEAN-74). + * Pick an agent, read its card, then connect (CLEAN-74, CLEAN-94). * * The preview step is the point of this component. Connecting a peer decides * what another agent will be told it can ask for, so the operator sees the * exact text first — a name alone would make "why did it pick the wrong one" * unanswerable later. + * + * The preview expands under the clicked row, accordion-style: at ten + * candidates a panel below the list sits under the fold, and a click that + * renders off-screen reads as a click that did nothing (CLEAN-94). */ const props = defineProps<{ open: boolean; agentId: string }>(); const emit = defineEmits<{ 'update:open': [value: boolean]; connected: [] }>(); @@ -64,8 +68,14 @@ watch( }, ); -async function select(candidate: IAgentPeerCandidate) { - if (candidate.connected) return; +async function toggle(candidate: IAgentPeerCandidate) { + if (candidate.connected || connecting.value) return; + if (selected.value?.id === candidate.id) { + selected.value = null; + preview.value = null; + error.value = null; + return; + } selected.value = candidate; preview.value = null; error.value = null; @@ -114,8 +124,8 @@ function statusVariant(status: string) { Add peer - Agents on this ranch this one isn't connected to yet. Connecting is - one-way. + Agents on this ranch this one isn't connected to yet. Click an agent + to read its card, then connect.
@@ -142,7 +152,7 @@ function statusVariant(status: string) { selected?.id === candidate.id ? 'bg-muted/60' : '', ]" :disabled="candidate.connected" - @click="select(candidate)" + @click="toggle(candidate)" > @@ -159,7 +169,46 @@ function statusVariant(status: string) { connected + + + Info + + + + +
+
+ + + +
+ +

{{ error }}

+
@@ -169,27 +218,6 @@ function statusVariant(status: string) {

No unconnected agents left on this ranch.

- -
-
- - - -
- -
- -

{{ error }}

From 8a7210d7b6db378be7f3a4c295b6820267c5cb1f Mon Sep 17 00:00:00 2001 From: "Maksym Hryzodub [DREAM]" Date: Wed, 16 Sep 2026 16:09:35 +0300 Subject: [PATCH 2/4] fix(admin): one-line relative times in the peer row and the feed (CLEAN-91) DateTimeAgo stacks the absolute date over the relative one, which wrapped awkwardly after the "card read" label and inside the feed's meta line. New formatTimeAgo renders a single relative phrase with the full date in the tooltip. Co-Authored-By: Claude Fable 5 --- .../agent/peer/components/peer/Delegations.vue | 4 +++- admin/slices/agent/peer/components/peer/Row.vue | 6 +++--- admin/slices/common/utils/formatDate.ts | 16 ++++++++++++++++ 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/admin/slices/agent/peer/components/peer/Delegations.vue b/admin/slices/agent/peer/components/peer/Delegations.vue index 6211f3ef..b8bb84ea 100644 --- a/admin/slices/agent/peer/components/peer/Delegations.vue +++ b/admin/slices/agent/peer/components/peer/Delegations.vue @@ -212,7 +212,9 @@ function toggle(id: string) {
- + + {{ formatTimeAgo(row.startedAt) }} + · {{ duration(row.durationMs) }} (() => { Advertises nothing — this agent has no way to tell when to ask it. - card read - + card read {{ formatTimeAgo(peer.cardReadAt) }}
diff --git a/admin/slices/common/utils/formatDate.ts b/admin/slices/common/utils/formatDate.ts index bc6cbbd4..885a0c60 100644 --- a/admin/slices/common/utils/formatDate.ts +++ b/admin/slices/common/utils/formatDate.ts @@ -8,3 +8,19 @@ export const formatDate = (date?: string) => { if (!date) return ''; return new Date(date).toLocaleDateString('en-US', { dateStyle: 'medium' }); }; + +/** Relative one-liner for inline meta ("card read 5 min ago") where the + * two-line DateTimeAgo would wrap; falls back to the plain date once the + * moment stops being news. Pair it with a formatDateTime tooltip. */ +export const formatTimeAgo = (date?: string) => { + if (!date) return ''; + const sec = Math.max(0, Math.round((Date.now() - new Date(date).getTime()) / 1000)); + if (sec < 60) return 'just now'; + const min = Math.round(sec / 60); + if (min < 60) return `${min} min ago`; + const hours = Math.round(min / 60); + if (hours < 24) return `${hours} h ago`; + const days = Math.round(hours / 24); + if (days < 7) return `${days} d ago`; + return formatDate(date); +}; From 9d4c17e29583b95f6214df3532eb9b962fc4d327 Mon Sep 17 00:00:00 2001 From: "Maksym Hryzodub [DREAM]" Date: Wed, 16 Sep 2026 16:36:50 +0300 Subject: [PATCH 3/4] refactor(admin): DateTimeAgoInline on vueuse instead of a hand-rolled helper (CLEAN-91) Same useTimeAgoIntl the stacked DateTimeAgo already uses, so the wording matches and the label keeps ticking without a re-render; drops the formatTimeAgo duplicate added one commit earlier. Co-Authored-By: Claude Fable 5 --- .../peer/components/peer/Delegations.vue | 4 +--- .../slices/agent/peer/components/peer/Row.vue | 7 ++----- .../common/components/date/TimeAgoInline.vue | 19 +++++++++++++++++++ admin/slices/common/utils/formatDate.ts | 16 ---------------- 4 files changed, 22 insertions(+), 24 deletions(-) create mode 100644 admin/slices/common/components/date/TimeAgoInline.vue diff --git a/admin/slices/agent/peer/components/peer/Delegations.vue b/admin/slices/agent/peer/components/peer/Delegations.vue index b8bb84ea..4f4da467 100644 --- a/admin/slices/agent/peer/components/peer/Delegations.vue +++ b/admin/slices/agent/peer/components/peer/Delegations.vue @@ -212,9 +212,7 @@ function toggle(id: string) {
- - {{ formatTimeAgo(row.startedAt) }} - + · {{ duration(row.durationMs) }} (() => { Advertises nothing — this agent has no way to tell when to ask it. - - card read {{ formatTimeAgo(peer.cardReadAt) }} + + card read
diff --git a/admin/slices/common/components/date/TimeAgoInline.vue b/admin/slices/common/components/date/TimeAgoInline.vue new file mode 100644 index 00000000..0b142675 --- /dev/null +++ b/admin/slices/common/components/date/TimeAgoInline.vue @@ -0,0 +1,19 @@ + + diff --git a/admin/slices/common/utils/formatDate.ts b/admin/slices/common/utils/formatDate.ts index 885a0c60..bc6cbbd4 100644 --- a/admin/slices/common/utils/formatDate.ts +++ b/admin/slices/common/utils/formatDate.ts @@ -8,19 +8,3 @@ export const formatDate = (date?: string) => { if (!date) return ''; return new Date(date).toLocaleDateString('en-US', { dateStyle: 'medium' }); }; - -/** Relative one-liner for inline meta ("card read 5 min ago") where the - * two-line DateTimeAgo would wrap; falls back to the plain date once the - * moment stops being news. Pair it with a formatDateTime tooltip. */ -export const formatTimeAgo = (date?: string) => { - if (!date) return ''; - const sec = Math.max(0, Math.round((Date.now() - new Date(date).getTime()) / 1000)); - if (sec < 60) return 'just now'; - const min = Math.round(sec / 60); - if (min < 60) return `${min} min ago`; - const hours = Math.round(min / 60); - if (hours < 24) return `${hours} h ago`; - const days = Math.round(hours / 24); - if (days < 7) return `${days} d ago`; - return formatDate(date); -}; From 9753b98ddacd3bb4b00f00edfec17de8cabdce39 Mon Sep 17 00:00:00 2001 From: "Maksym Hryzodub [DREAM]" Date: Wed, 16 Sep 2026 16:37:08 +0300 Subject: [PATCH 4/4] chore: bump version to 0.3.52 (CLEAN-94) 0.3.51 is already tagged; a matching tag would silently skip the release run. Co-Authored-By: Claude Fable 5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 81b22b7d..64fab2a1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ranch", - "version": "0.3.50", + "version": "0.3.52", "private": true, "packageManager": "bun@1.2.12", "workspaces": [