[P1] Exchange table readability: IDs, attributes, agent names #105

Open
opened 2026-04-01 22:52:46 +02:00 by claude · 2 comments
Owner

Parent Epic

#100

Problem

The exchanges table — the most-used view in the product — has three readability problems that make it look unfinished and hard to scan:

  1. Exchange IDs are 34-character hex strings that dominate the table
  2. Attributes column shows "—" for every row, wasting space
  3. Agent names are full Kubernetes pod names, unreadable

Current State (screenshot reference: ux-audit/01-exchanges-list.png)

STATUS  ROUTE              APP          ATTRIBUTES  EXCHANGE ID                         STARTED              DURATION  AGENT
● OK    route3             backend-app  —           73A3A974C735725-00000000000000B3    2026-04-01 22:31:41  178ms     cameleer3-backend-7c778f488c-2c2pc-1
● OK    route2             backend-app  —           73A3A974C735725-00000000000000B2    2026-04-01 22:31:41  36ms      cameleer3-backend-7c778f488c-2c2pc-1
● OK    try-catch-test     quarkus-app  —           2FE210D614787B6-000000000000049B    2026-04-01 22:31:41  0ms       cameleer3-quarkus-76b58fd569-8hr72-1

Proposed Solution

1. Exchange ID — Truncate + Copy

Show only the last 8 characters with a copy button on hover:

EXCHANGE ID
···000000B3  📋    (hover shows full ID, click copies)
···000000B2  📋
···0000049B  📋

Or use a short hash display like Datadog/Sentry:

EXCHANGE ID
B3 (route3)    ← combine with route as context

Consider making Exchange ID a secondary column (narrower, lighter text weight) since Route + App + Timestamp already identify an exchange uniquely for scanning.

2. Attributes — Show or Hide

Two options:

Option A: Hide when empty. If no exchanges have attributes, remove the column entirely. Add it back dynamically when attributes appear. This reclaims ~120px of horizontal space.

Option B: Show as colored badges. When attributes exist, display them as compact color-coded badges (the infrastructure for this exists — attribute-color.ts):

ATTRIBUTES
orderId=4521  priority=HIGH
region=eu-west  env=prod
—

Recommendation: Option A for now, with Option B when attributes are populated. The current "—" everywhere looks broken.

3. Agent Names — Display Name / Truncation

Agent column currently shows full K8s pod names like cameleer3-backend-7c778f488c-2c2pc-1. Proposal:

AGENT
backend-1      (derived: strip app prefix, use pod suffix)
backend-2
quarkus-1
sample-1

Derivation logic:

  • If agent has a displayName from registration → use it
  • Otherwise: strip the app-name prefix, extract the unique pod suffix
  • Show full name on hover tooltip
  • In the Runtime instance page, show the full name prominently

Alternatively, show the deployment name + instance number:

backend (2c2pc)
backend (nlfz9)
quarkus (8hr72)

4. Duration Column — Color Coding

Currently duration values are shown in amber for all rows. Consider color-coding by performance:

< 100ms     → green (var(--success))
100-500ms   → default text color
500ms-2s    → amber (var(--amber))
> 2s        → red (var(--error))

This gives instant visual scanning for slow exchanges without needing to read numbers.

5. Column Priority for Narrow Viewports

If the viewport is narrower than ideal, consider hiding columns in this order (least important first):

  1. Exchange ID (redundant with row click)
  2. Agent (less useful for debugging)
  3. Application (visible in sidebar scope)

Acceptance Criteria

  • Exchange IDs truncated to last 8 chars with copy-on-click
  • Full ID shown on hover tooltip
  • Attributes column hidden when all values are empty
  • Attributes shown as colored badges when populated
  • Agent names use display name or truncated format
  • Full agent name in hover tooltip
  • Duration values color-coded by performance tier
  • #90 (Backend data gaps — may include attribute capture)
## Parent Epic #100 ## Problem The exchanges table — the most-used view in the product — has three readability problems that make it look unfinished and hard to scan: 1. **Exchange IDs** are 34-character hex strings that dominate the table 2. **Attributes column** shows "—" for every row, wasting space 3. **Agent names** are full Kubernetes pod names, unreadable ## Current State (screenshot reference: `ux-audit/01-exchanges-list.png`) ``` STATUS ROUTE APP ATTRIBUTES EXCHANGE ID STARTED DURATION AGENT ● OK route3 backend-app — 73A3A974C735725-00000000000000B3 2026-04-01 22:31:41 178ms cameleer3-backend-7c778f488c-2c2pc-1 ● OK route2 backend-app — 73A3A974C735725-00000000000000B2 2026-04-01 22:31:41 36ms cameleer3-backend-7c778f488c-2c2pc-1 ● OK try-catch-test quarkus-app — 2FE210D614787B6-000000000000049B 2026-04-01 22:31:41 0ms cameleer3-quarkus-76b58fd569-8hr72-1 ``` ## Proposed Solution ### 1. Exchange ID — Truncate + Copy Show only the last 8 characters with a copy button on hover: ``` EXCHANGE ID ···000000B3 📋 (hover shows full ID, click copies) ···000000B2 📋 ···0000049B 📋 ``` Or use a short hash display like Datadog/Sentry: ``` EXCHANGE ID B3 (route3) ← combine with route as context ``` Consider making Exchange ID a secondary column (narrower, lighter text weight) since Route + App + Timestamp already identify an exchange uniquely for scanning. ### 2. Attributes — Show or Hide Two options: **Option A: Hide when empty.** If no exchanges have attributes, remove the column entirely. Add it back dynamically when attributes appear. This reclaims ~120px of horizontal space. **Option B: Show as colored badges.** When attributes exist, display them as compact color-coded badges (the infrastructure for this exists — `attribute-color.ts`): ``` ATTRIBUTES orderId=4521 priority=HIGH region=eu-west env=prod — ``` Recommendation: **Option A** for now, with Option B when attributes are populated. The current "—" everywhere looks broken. ### 3. Agent Names — Display Name / Truncation Agent column currently shows full K8s pod names like `cameleer3-backend-7c778f488c-2c2pc-1`. Proposal: ``` AGENT backend-1 (derived: strip app prefix, use pod suffix) backend-2 quarkus-1 sample-1 ``` **Derivation logic:** - If agent has a `displayName` from registration → use it - Otherwise: strip the app-name prefix, extract the unique pod suffix - Show full name on hover tooltip - In the Runtime instance page, show the full name prominently Alternatively, show the deployment name + instance number: ``` backend (2c2pc) backend (nlfz9) quarkus (8hr72) ``` ### 4. Duration Column — Color Coding Currently duration values are shown in amber for all rows. Consider color-coding by performance: ``` < 100ms → green (var(--success)) 100-500ms → default text color 500ms-2s → amber (var(--amber)) > 2s → red (var(--error)) ``` This gives instant visual scanning for slow exchanges without needing to read numbers. ### 5. Column Priority for Narrow Viewports If the viewport is narrower than ideal, consider hiding columns in this order (least important first): 1. Exchange ID (redundant with row click) 2. Agent (less useful for debugging) 3. Application (visible in sidebar scope) ## Acceptance Criteria - [ ] Exchange IDs truncated to last 8 chars with copy-on-click - [ ] Full ID shown on hover tooltip - [ ] Attributes column hidden when all values are empty - [ ] Attributes shown as colored badges when populated - [ ] Agent names use display name or truncated format - [ ] Full agent name in hover tooltip - [ ] Duration values color-coded by performance tier ## Related Issues - #90 (Backend data gaps — may include attribute capture)
claude added the uiuxpmf labels 2026-04-01 22:52:46 +02:00
Author
Owner

Design Specification

Exchange ID Truncation

Show last 8 characters with ellipsis prefix: ···000000B3. Full ID on hover tooltip. Click copies full ID to clipboard with toast. Use MonoText component, lighter text weight (var(--text-secondary)).

Attributes Column

Dynamic visibility: Hide column entirely when all rows have empty attributes (data.every(r => !r.attributes?.length)). Show as colored badges when populated, using existing attribute-color.ts for color assignment: <Badge label="orderId=4521" color={getAttributeColor('orderId')} />.

Agent Name Display

Derivation algorithm:

  1. If agent has displayName from registration → use it
  2. Else: strip app-name prefix from pod name, extract unique suffix
    • cameleer3-backend-7c778f488c-2c2pc-1backend (2c2pc)
  3. Full name shown on hover tooltip

Duration Color Coding

Range Color CSS Variable
< 100ms green var(--success)
100-500ms default var(--text-primary)
500ms-2s amber var(--amber)
> 2s red var(--error)

Apply via custom cell renderer in DataTable.

## Design Specification ### Exchange ID Truncation Show last 8 characters with ellipsis prefix: `···000000B3`. Full ID on hover tooltip. Click copies full ID to clipboard with toast. Use `MonoText` component, lighter text weight (`var(--text-secondary)`). ### Attributes Column **Dynamic visibility**: Hide column entirely when all rows have empty attributes (`data.every(r => !r.attributes?.length)`). Show as colored badges when populated, using existing `attribute-color.ts` for color assignment: `<Badge label="orderId=4521" color={getAttributeColor('orderId')} />`. ### Agent Name Display Derivation algorithm: 1. If agent has `displayName` from registration → use it 2. Else: strip app-name prefix from pod name, extract unique suffix - `cameleer3-backend-7c778f488c-2c2pc-1` → `backend (2c2pc)` 3. Full name shown on hover tooltip ### Duration Color Coding | Range | Color | CSS Variable | |-------|-------|-------------| | < 100ms | green | `var(--success)` | | 100-500ms | default | `var(--text-primary)` | | 500ms-2s | amber | `var(--amber)` | | > 2s | red | `var(--error)` | Apply via custom cell renderer in DataTable.
Author
Owner

Design Specification

1. Exchange ID Truncation

Show last 8 chars with ellipsis: ...000000B3. Full ID on hover tooltip. Click copies to clipboard with toast.

New component ExchangeIdCell:

function ExchangeIdCell({ id }: { id: string }) {
  const { toast } = useToast();
  const handleCopy = async (e: React.MouseEvent) => {
    e.stopPropagation(); // prevent row click
    await navigator.clipboard.writeText(id);
    toast({ title: 'Copied', description: id, variant: 'success', duration: 2000 });
  };
  return (
    <Tooltip content={<><MonoText size="xs">{id}</MonoText><br/>Click to copy</>}>
      <span className={styles.exchangeIdCell} onClick={handleCopy}>
        <MonoText size="xs">{'\u2026' + id.slice(-8)}</MonoText>
        <Copy size={11} className={styles.copyIcon} />
      </span>
    </Tooltip>
  );
}

Column width: explicit 130px (was unbounded auto).


2. Attributes Column — Dynamic Visibility

Hide column entirely when all rows have empty attributes:

const hasAnyAttributes = useMemo(
  () => rows.some(r => r.attributes && Object.keys(r.attributes).length > 0),
  [rows]
);
const columns = useMemo(() => {
  const base = buildBaseColumns();
  return hasAnyAttributes ? base : base.filter(c => c.key !== 'attributes');
}, [hasAnyAttributes]);

When populated: render as Badge components using existing attributeBadgeColor(). Empty rows render null (not ).


3. Agent Name Display

Derivation algorithm (ui/src/utils/agent-name.ts):

export function deriveAgentShortName(instanceId: string, applicationId?: string): string {
  let name = instanceId;
  if (name.startsWith('cameleer3-')) name = name.slice(9);
  const appPrefix = applicationId?.replace(/-app$/, '') ?? '';
  if (appPrefix && name.startsWith(appPrefix + '-')) name = name.slice(appPrefix.length + 1);
  const parts = name.split('-');
  if (parts.length >= 3) {
    return `${parts[0]}-${parts.slice(-2).join('-')}`;
  }
  return name.length > 20 ? name.slice(0, 17) + '\u2026' : name;
}
Full instanceId applicationId Display
cameleer3-backend-7c778f488c-2c2pc-1 backend-app backend-2c2pc-1
cameleer3-quarkus-76b58fd569-8hr72-1 quarkus-app quarkus-8hr72-1

Full name on hover via Tooltip.


4. Duration Color Coding

Duration Color CSS Variable
< 100ms green var(--success)
100-499ms default var(--text-secondary)
500ms-1.99s amber var(--amber)
>= 2s red var(--error)
FAILED status red var(--error)

Updated durationClass():

function durationClass(ms: number, status: string): string {
  if (status === 'FAILED') return styles.durBreach;
  if (ms < 100) return styles.durFast;
  if (ms < 500) return styles.durNormal;
  if (ms < 2000) return styles.durSlow;
  return styles.durBreach;
}

5. Responsive Column Hiding

Viewport Hidden
>= 1200px None
900-1199px Exchange ID
700-899px Exchange ID, Agent
< 700px Exchange ID, Agent, Application

Via CSS media queries on nth-child selectors in Dashboard.module.css.


Files Modified

File Changes
Dashboard.tsx ExchangeIdCell component, dynamic attributes column, agent name derivation, duration class update
Dashboard.module.css .exchangeIdCell, .copyIcon, .durSlow→amber, responsive media queries
ui/src/utils/agent-name.ts New file: deriveAgentShortName()

No design system changes required. All customization via existing render functions on DataTable columns.

## Design Specification ### 1. Exchange ID Truncation Show last 8 chars with ellipsis: `...000000B3`. Full ID on hover tooltip. Click copies to clipboard with toast. **New component `ExchangeIdCell`:** ```typescript function ExchangeIdCell({ id }: { id: string }) { const { toast } = useToast(); const handleCopy = async (e: React.MouseEvent) => { e.stopPropagation(); // prevent row click await navigator.clipboard.writeText(id); toast({ title: 'Copied', description: id, variant: 'success', duration: 2000 }); }; return ( <Tooltip content={<><MonoText size="xs">{id}</MonoText><br/>Click to copy</>}> <span className={styles.exchangeIdCell} onClick={handleCopy}> <MonoText size="xs">{'\u2026' + id.slice(-8)}</MonoText> <Copy size={11} className={styles.copyIcon} /> </span> </Tooltip> ); } ``` Column width: explicit `130px` (was unbounded auto). --- ### 2. Attributes Column — Dynamic Visibility Hide column entirely when all rows have empty attributes: ```typescript const hasAnyAttributes = useMemo( () => rows.some(r => r.attributes && Object.keys(r.attributes).length > 0), [rows] ); const columns = useMemo(() => { const base = buildBaseColumns(); return hasAnyAttributes ? base : base.filter(c => c.key !== 'attributes'); }, [hasAnyAttributes]); ``` When populated: render as `Badge` components using existing `attributeBadgeColor()`. Empty rows render `null` (not `—`). --- ### 3. Agent Name Display **Derivation algorithm** (`ui/src/utils/agent-name.ts`): ```typescript export function deriveAgentShortName(instanceId: string, applicationId?: string): string { let name = instanceId; if (name.startsWith('cameleer3-')) name = name.slice(9); const appPrefix = applicationId?.replace(/-app$/, '') ?? ''; if (appPrefix && name.startsWith(appPrefix + '-')) name = name.slice(appPrefix.length + 1); const parts = name.split('-'); if (parts.length >= 3) { return `${parts[0]}-${parts.slice(-2).join('-')}`; } return name.length > 20 ? name.slice(0, 17) + '\u2026' : name; } ``` | Full instanceId | applicationId | Display | |---|---|---| | `cameleer3-backend-7c778f488c-2c2pc-1` | `backend-app` | `backend-2c2pc-1` | | `cameleer3-quarkus-76b58fd569-8hr72-1` | `quarkus-app` | `quarkus-8hr72-1` | Full name on hover via `Tooltip`. --- ### 4. Duration Color Coding | Duration | Color | CSS Variable | |----------|-------|-------------| | < 100ms | green | `var(--success)` | | 100-499ms | default | `var(--text-secondary)` | | 500ms-1.99s | amber | `var(--amber)` | | >= 2s | red | `var(--error)` | | FAILED status | red | `var(--error)` | Updated `durationClass()`: ```typescript function durationClass(ms: number, status: string): string { if (status === 'FAILED') return styles.durBreach; if (ms < 100) return styles.durFast; if (ms < 500) return styles.durNormal; if (ms < 2000) return styles.durSlow; return styles.durBreach; } ``` --- ### 5. Responsive Column Hiding | Viewport | Hidden | |----------|--------| | >= 1200px | None | | 900-1199px | Exchange ID | | 700-899px | Exchange ID, Agent | | < 700px | Exchange ID, Agent, Application | Via CSS media queries on `nth-child` selectors in `Dashboard.module.css`. --- ### Files Modified | File | Changes | |---|---| | `Dashboard.tsx` | ExchangeIdCell component, dynamic attributes column, agent name derivation, duration class update | | `Dashboard.module.css` | .exchangeIdCell, .copyIcon, .durSlow→amber, responsive media queries | | `ui/src/utils/agent-name.ts` | New file: `deriveAgentShortName()` | No design system changes required. All customization via existing `render` functions on DataTable columns.
Sign in to join this conversation.