Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion app/pages/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,6 @@ const homepageTrendingSelection = computed(() => selectHomepageTrendingSkills(
name: skill.name,
displayName: skill.displayName,
registryPath: skill.registryPath,
maintainerName: repo.evidence?.authorName ?? null,
description: skill.description,
context: trendingShareLabel(repo.authorCount),
})),
Expand Down
8 changes: 6 additions & 2 deletions app/utils/homepage-person-skills.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,14 @@ export function uniqueByOwner(items: readonly SkillSourceItem[]): SkillSourceIte
* the caller then loads the live person feed instead.
*/
export function selectHomepageTrendingSkills(
trending: readonly SkillSourceItem[],
trending: readonly Omit<SkillSourceItem, 'maintainerName'>[],
fallbacks: readonly SkillSourceItem[],
): HomepageTrendingSelection {
const unique = uniqueByOwner(trending)
const namesByOwner = new Map(fallbacks.map(item => [item.owner, item.maintainerName ?? item.owner]))
const unique = uniqueByOwner(trending.map(item => ({
...item,
maintainerName: namesByOwner.get(item.owner) ?? item.owner,
})))
if (unique.length < HOMEPAGE_TRENDING_MINIMUM)
return { _tag: 'fallback' }

Expand Down
2 changes: 2 additions & 0 deletions layers/identity/app/pages/me/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,7 @@ async function deleteAccount() {
<form v-show="showEmail" id="email-settings-form" class="mt-4 space-y-4" @submit.prevent="saveEmail">
<UFormField label="Email address" name="email" :error="emailMissingAddress ? 'Add a valid email address, or turn off both emails.' : undefined">
<UInput
id="account-digest-email"
v-model="emailForm.digest_email"
type="email"
autocomplete="email"
Expand Down Expand Up @@ -662,6 +663,7 @@ async function deleteAccount() {
</p>
<UFormField :label="`Type ${me?.login} to confirm`" name="confirm_login">
<UInput
id="account-confirm-login"
v-model="deleteConfirmation"
autocomplete="off"
autocapitalize="off"
Expand Down
8 changes: 5 additions & 3 deletions layers/identity/app/pages/onboarding/discover.vue
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ useSeoMeta({ title: 'Discover skills', robots: 'noindex' })
<UButton
:loading="syncing"
size="sm"
class="min-h-11"
color="neutral"
variant="outline"
icon="i-lucide-refresh-cw"
Expand Down Expand Up @@ -160,7 +161,7 @@ useSeoMeta({ title: 'Discover skills', robots: 'noindex' })
<template v-else>
None of your starred repos with "skill" in the name are in the registry yet.
</template>
<NuxtLink to="/onboarding/email" class="underline">
<NuxtLink to="/onboarding/email" class="inline-flex min-h-11 items-center underline">
skip ahead
</NuxtLink>.
</div>
Expand Down Expand Up @@ -192,7 +193,7 @@ useSeoMeta({ title: 'Discover skills', robots: 'noindex' })
type="checkbox"
:checked="r.watching || selected.has(`${r.owner}/${r.repo}`)"
:disabled="r.watching"
class="mt-1"
class="mt-1 size-4 accent-primary"
@change="toggle(`${r.owner}/${r.repo}`)"
>
<div class="min-w-0 flex-1">
Expand All @@ -219,7 +220,7 @@ useSeoMeta({ title: 'Discover skills', robots: 'noindex' })
</template>

<div class="mt-8 flex items-center justify-between">
<NuxtLink to="/onboarding/email" class="inline-flex min-h-11 items-center font-mono text-sm text-muted hover:text-default underline">
<NuxtLink to="/onboarding/email" class="inline-flex min-h-11 min-w-11 items-center font-mono text-sm text-muted hover:text-default underline">
Skip
</NuxtLink>
<UButton
Expand All @@ -228,6 +229,7 @@ useSeoMeta({ title: 'Discover skills', robots: 'noindex' })
:label="selected.size ? `Watch ${selected.size}` : 'Continue'"
trailing-icon="i-lucide-arrow-right"
size="sm"
class="min-h-11"
@click="watchSelected"
/>
</div>
Expand Down
4 changes: 2 additions & 2 deletions layers/identity/app/pages/onboarding/email.vue
Original file line number Diff line number Diff line change
Expand Up @@ -93,14 +93,14 @@ useSeoMeta({ title: 'Email opt-in', robots: 'noindex' })
</div>

<label class="flex min-h-11 items-start gap-3 cursor-pointer">
<input v-model="weeklyOptIn" type="checkbox" class="mt-0.5">
<input v-model="weeklyOptIn" type="checkbox" class="mt-0.5 size-4 accent-primary">
<span class="text-sm text-muted leading-relaxed">
Send me distinct trending Skills each Monday.
</span>
</label>

<label class="flex min-h-11 items-start gap-3 cursor-pointer">
<input v-model="optIn" type="checkbox" class="mt-0.5">
<input v-model="optIn" type="checkbox" class="mt-0.5 size-4 accent-primary">
<span class="text-sm text-muted leading-relaxed">
Send me watched changes once a month.
</span>
Expand Down
11 changes: 11 additions & 0 deletions test/unit/me-watchlist.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,17 @@ describe('account skill watchlist', () => {
expect(disabledWhilePending).toBe(true)
})

it('connects the email address label to its textbox', async () => {
const wrapper = await mountPage()
document.body.append(wrapper.element)
await wrapper.get('button[aria-controls="email-settings-form"]').trigger('click')
const input = wrapper.get('input[type="email"]').element as HTMLInputElement

expect([...input.labels ?? []].map(label => label.textContent)).toContain('Email address')
wrapper.element.remove()
wrapper.unmount()
})

it('saves the weekly email and monthly digest as separate choices', async () => {
const wrapper = await mountPage()
await wrapper.get('button[aria-controls="email-settings-form"]').trigger('click')
Expand Down
19 changes: 19 additions & 0 deletions test/unit/skill-source-list.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,25 @@ describe('skill source list', () => {
expect(selectHomepageTrendingSkills(items, homepagePersonSkillFallbacks)).toEqual({ _tag: 'fallback' })
})

it('attributes trending skills to repository maintainers', () => {
const items = Array.from({ length: HOMEPAGE_TRENDING_MINIMUM }, (_, index) => ({
owner: index === 0 ? 'addyosmani' : `author-${index}`,
repo: 'skills',
name: 'api-and-interface-design',
displayName: 'API and interface design',
registryPath: `/gh/${index === 0 ? 'addyosmani' : `author-${index}`}/skills/api-and-interface-design`,
maintainerName: 'Yarchi',
}))

const selection = selectHomepageTrendingSkills(items, homepagePersonSkillFallbacks)
expect(selection._tag).toBe('trending')
if (selection._tag !== 'trending')
return

expect(selection.items[0]?.maintainerName).toBe('Addy Osmani')
expect(selection.items[1]?.maintainerName).toBe('author-1')
})

it('preserves a focused item until live data can replace it safely', async () => {
const container = document.createElement('div')
const outsideButton = document.createElement('button')
Expand Down