feat(drafts): drop the authed user's circle, mark own drafts with a '*' suffix

Three tweaks to the home-page Draft badge:

1. Filter the authed user out of `draft_users` before rendering
   circles. The row already signals 'this user has a draft' via the
   asterisk (below), so a circle for them would be redundant noise.
   New `currentUsername` prop on DraftBadge — pass
   `$userStore?.username` from each row. The tooltip still lists every
   user (with `(you)` next to the authed one) so the full picture is
   one hover away.

2. The badge already showed whenever `is_draft || draft_users.length > 0`
   (per-user OR any-user). Spelled the rationale out in a comment —
   no logic change.

3. Append '*' to the displayed summary when `is_draft` is true. Falls
   back to `draft_path`/`path` when summary is empty so the marker
   never decorates an empty string. Threaded the same expression into
   ScriptRow / FlowRow / AppRow.

Slice/overflow math now keys on the post-filter `otherUsers` list, so
dropping the authed user doesn't silently shrink the visible count
(e.g. 3 users incl. self → 2 circles, not 1 circle + a '+1' bubble).
This commit is contained in:
Diego Imbert
2026-06-09 15:07:11 +02:00
parent dff282fc48
commit e6cef5a7e3
4 changed files with 46 additions and 11 deletions
+33 -7
View File
@@ -23,9 +23,26 @@
is_draft?: boolean
draft_only?: boolean
draft_users?: DraftUser[]
/** Authed user's workspace username — circles for THIS user are
* omitted because the row already signals their own draft via
* the asterisk appended to the displayed summary. Pass
* `$userStore?.username` from the row. */
currentUsername?: string | null
}
let { is_draft = false, draft_only = false, draft_users = [] }: Props = $props()
let {
is_draft = false,
draft_only = false,
draft_users = [],
currentUsername = undefined
}: Props = $props()
// Drop the authed user from the circle row — their own draft is
// signalled by the asterisk on the row's summary, so showing both
// would be visual noise. Keep the legacy NULL-username row.
const otherUsers = $derived(
currentUsername ? draft_users.filter((u) => u.username !== currentUsername) : draft_users
)
/** Two-letter uppercase initials from a username — `john.doe`/`john_doe` →
* `JD`, `alice` → `AL`, the legacy NULL-email row (no username) → `?`. */
@@ -60,15 +77,22 @@
return PALETTE[hash % PALETTE.length]
}
// First 3 circles when ≤3 users; first 2 + a "+N" overflow when 4+.
// First 3 circles when ≤3 OTHER users; first 2 + a "+N" overflow
// when 4+. The slice/overflow math applies to `otherUsers` (post
// current-user filter), not the raw `draft_users` — otherwise
// dropping the authed user would silently change the visible count.
const MAX_CIRCLES = 3
const visibleUsers = $derived(
draft_users.length <= MAX_CIRCLES ? draft_users : draft_users.slice(0, MAX_CIRCLES - 1)
otherUsers.length <= MAX_CIRCLES ? otherUsers : otherUsers.slice(0, MAX_CIRCLES - 1)
)
const overflowCount = $derived(
draft_users.length > MAX_CIRCLES ? draft_users.length - (MAX_CIRCLES - 1) : 0
otherUsers.length > MAX_CIRCLES ? otherUsers.length - (MAX_CIRCLES - 1) : 0
)
// Show the badge whenever ANY draft exists (`draft_users` non-empty)
// OR when the authed user has a draft (`is_draft` true — the list
// endpoint sets this even for paths the user has a draft on but no
// one else does).
const showBadge = $derived(is_draft || draft_users.length > 0)
</script>
@@ -79,7 +103,7 @@
{draft_only ? 'Never deployed — only a draft exists.' : 'Deployed with drafts pending.'}
<div class="mt-1 flex flex-col gap-0.5">
{#each draft_users as u}
<span>{fullLabel(u)}</span>
<span>{fullLabel(u)}{u.username === currentUsername ? ' (you)' : ''}</span>
{/each}
</div>
{:else if draft_only}
@@ -89,11 +113,13 @@
{/if}
{/snippet}
<Badge small color="indigo">
{#if draft_users.length > 0}
{#if otherUsers.length > 0}
<!-- Circles sit inside the Badge, before the label. `-space-x-1`
overlaps them slightly; each circle's ring uses the badge's
indigo tint instead of plain white so the overlap reads as
intentional rather than a stack-of-floating-dots. -->
intentional rather than a stack-of-floating-dots. Only OTHER
users get a circle — the authed user's own draft is
signalled by the row's asterisk. -->
<span class="flex -space-x-1">
{#each visibleUsers as u}
<span
@@ -105,7 +105,7 @@
kind="app"
{marked}
path={(app as any).draft_path ?? app.path}
summary={app.summary}
summary={app.is_draft ? `${app.summary || (app as any).draft_path || app.path}*` : app.summary}
workspaceId={app.workspace_id ?? $workspaceStore ?? ''}
canFavorite={!app.draft_only}
{depth}
@@ -119,7 +119,12 @@
<Badge small icon={{ icon: FileJson }}>Raw</Badge>
{/if}
<SharedBadge canWrite={app.canWrite} extraPerms={app.extra_perms} />
<DraftBadge is_draft={app.is_draft} draft_only={app.draft_only} draft_users={app.draft_users} />
<DraftBadge
is_draft={app.is_draft}
draft_only={app.draft_only}
draft_users={app.draft_users}
currentUsername={$userStore?.username}
/>
{#if app.labels?.length}
<div class="flex items-center gap-0.5">
{#each app.labels.slice(0, 3) as label}
@@ -130,7 +130,7 @@
workspaceId={flow.workspace_id ?? $workspaceStore ?? ''}
{marked}
path={flow.draft_path ?? flow.path}
summary={flow.summary}
summary={flow.is_draft ? `${flow.summary || flow.draft_path || flow.path}*` : flow.summary}
{errorHandlerMuted}
canFavorite={!flow.draft_only}
{depth}
@@ -145,6 +145,7 @@
is_draft={flow.is_draft}
draft_only={flow.draft_only}
draft_users={flow.draft_users}
currentUsername={$userStore?.username}
/>
{#if flow.labels?.length}
<div class="flex items-center gap-0.5">
@@ -147,7 +147,9 @@
kind="script"
{marked}
path={script.draft_path ?? script.path}
summary={script.summary}
summary={script.is_draft
? `${script.summary || script.draft_path || script.path}*`
: script.summary}
{errorHandlerMuted}
workspaceId={$workspaceStore ?? ''}
canFavorite={!script.draft_only}
@@ -192,6 +194,7 @@
is_draft={script.is_draft}
draft_only={script.draft_only}
draft_users={script.draft_users}
currentUsername={$userStore?.username}
/>
{#if script.labels?.length}
<div class="flex items-center gap-0.5">