|
| 1 | +<script lang="ts"> |
| 2 | + // The coverage column — the front of the workbench flow. Every org the |
| 3 | + // workspace client can see, filterable by name/slug, sorted by corpus |
| 4 | + // count (fewest first by default — the operator's job here is to find |
| 5 | + // orgs that could and should have more corpus content). Zero-corpus rows |
| 6 | + // wear a red badge. Clicking a row opens the org card; writes elsewhere |
| 7 | + // in the workbench refresh the counts via augment-it:entity-updated. |
| 8 | + // Per gh #32 (layer 2 of the corpus-coverage issue). |
| 9 | +
|
| 10 | + import { fetchOrgRoster } from './lib/org-client'; |
| 11 | + import type { OrgRosterRow } from './lib/types'; |
| 12 | +
|
| 13 | + let { |
| 14 | + client, |
| 15 | + activeSlug, |
| 16 | + onpick, |
| 17 | + }: { |
| 18 | + client: string; |
| 19 | + activeSlug: string | null; |
| 20 | + onpick: (org_slug: string) => void; |
| 21 | + } = $props(); |
| 22 | +
|
| 23 | + let rows = $state<OrgRosterRow[]>([]); |
| 24 | + let filter = $state(''); |
| 25 | + let fewestFirst = $state(true); |
| 26 | + let loading = $state(false); |
| 27 | + let error = $state<string | null>(null); |
| 28 | +
|
| 29 | + async function load(c: string) { |
| 30 | + loading = true; |
| 31 | + error = null; |
| 32 | + try { |
| 33 | + rows = await fetchOrgRoster(c); |
| 34 | + } catch (err) { |
| 35 | + error = err instanceof Error ? err.message : String(err); |
| 36 | + rows = []; |
| 37 | + } finally { |
| 38 | + loading = false; |
| 39 | + } |
| 40 | + } |
| 41 | +
|
| 42 | + // Reload whenever the workspace client changes (the default filter IS the |
| 43 | + // workspace: client_access CONTAINS <active client>). |
| 44 | + $effect(() => { |
| 45 | + void load(client); |
| 46 | + }); |
| 47 | +
|
| 48 | + // Any write anywhere in the workbench may have changed a count. |
| 49 | + $effect(() => { |
| 50 | + const refresh = () => void load(client); |
| 51 | + window.addEventListener('augment-it:entity-updated', refresh); |
| 52 | + return () => window.removeEventListener('augment-it:entity-updated', refresh); |
| 53 | + }); |
| 54 | +
|
| 55 | + const visible = $derived.by(() => { |
| 56 | + const q = filter.trim().toLowerCase(); |
| 57 | + const filtered = q |
| 58 | + ? rows.filter((r) => |
| 59 | + `${r.complete_name ?? ''} ${r.conventional_name ?? ''} ${r.slug}` |
| 60 | + .toLowerCase() |
| 61 | + .includes(q), |
| 62 | + ) |
| 63 | + : rows; |
| 64 | + return [...filtered].sort((a, b) => |
| 65 | + fewestFirst ? a.corpus_count - b.corpus_count : b.corpus_count - a.corpus_count, |
| 66 | + ); |
| 67 | + }); |
| 68 | +
|
| 69 | + function displayName(r: OrgRosterRow): string { |
| 70 | + return r.complete_name ?? r.conventional_name ?? r.slug; |
| 71 | + } |
| 72 | +</script> |
| 73 | + |
| 74 | +<aside class="ow-roster"> |
| 75 | + <div class="ow-roster-head"> |
| 76 | + <input |
| 77 | + class="ow-roster-filter" |
| 78 | + type="search" |
| 79 | + placeholder="Filter {rows.length} orgs…" |
| 80 | + bind:value={filter} |
| 81 | + /> |
| 82 | + <button |
| 83 | + type="button" |
| 84 | + class="ow-roster-sort" |
| 85 | + title="Sort by corpus count" |
| 86 | + onclick={() => (fewestFirst = !fewestFirst)} |
| 87 | + > |
| 88 | + corpus {fewestFirst ? '↑' : '↓'} |
| 89 | + </button> |
| 90 | + </div> |
| 91 | + |
| 92 | + {#if loading && rows.length === 0} |
| 93 | + <p class="ow-roster-note">loading roster…</p> |
| 94 | + {:else if error} |
| 95 | + <div class="ow-error">{error}</div> |
| 96 | + {:else if visible.length === 0} |
| 97 | + <p class="ow-roster-note">no orgs match</p> |
| 98 | + {:else} |
| 99 | + <ul class="ow-roster-list"> |
| 100 | + {#each visible as r (r.slug)} |
| 101 | + <li> |
| 102 | + <button |
| 103 | + type="button" |
| 104 | + class="ow-roster-row" |
| 105 | + class:active={r.slug === activeSlug} |
| 106 | + onclick={() => onpick(r.slug)} |
| 107 | + > |
| 108 | + <span class="ow-roster-name">{displayName(r)}</span> |
| 109 | + <span class="ow-roster-counts"> |
| 110 | + <span class="ow-roster-corpus" class:zero={r.corpus_count === 0}> |
| 111 | + {r.corpus_count} corpus |
| 112 | + </span> |
| 113 | + · {r.link_count} links · {r.stream_count} streams · {r.people_count} people |
| 114 | + </span> |
| 115 | + </button> |
| 116 | + </li> |
| 117 | + {/each} |
| 118 | + </ul> |
| 119 | + {/if} |
| 120 | +</aside> |
0 commit comments