From f2205675d7f906f77dd050b04ae823320c0e76c9 Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Mon, 12 Oct 2026 17:04:17 +1100 Subject: [PATCH 1/4] fix(identity): clarify onboarding actions and announcements --- COPY.md | 1 + .../app/components/OnboardingSteps.vue | 1 + layers/identity/app/pages/me/index.vue | 5 +-- .../app/pages/onboarding/discover.vue | 27 ++++++++-------- test/unit/login-interactions.nuxt.test.ts | 32 +++++++++++++++++++ 5 files changed, 51 insertions(+), 15 deletions(-) diff --git a/COPY.md b/COPY.md index cbc4cecc..841a5737 100644 --- a/COPY.md +++ b/COPY.md @@ -201,6 +201,7 @@ Use `7-day social mentions · {date}` as the footer. Format the UTC date as `2 O | Star import date | Last complete GitHub import: {date} | A bounded partial import has no completion date. | | Star import partial | Checked the first 1,000 GitHub stars. Some starred Repositories were not checked. | The page limit leaves coverage partial. | | Account without likes, with watches | Your watched Repositories are ready · {n} watched {Repository/Repositories}. Like a Skill to keep it here. · Browse skills · View watched Repositories | Show existing account value when the liked Skills view is empty. | +| Stop watching a Repository | Stop watching {owner}/{repo} | Remove the Repository watch. Email preferences stay separate. | ## Register by context diff --git a/layers/identity/app/components/OnboardingSteps.vue b/layers/identity/app/components/OnboardingSteps.vue index a4419f83..317699c1 100644 --- a/layers/identity/app/components/OnboardingSteps.vue +++ b/layers/identity/app/components/OnboardingSteps.vue @@ -12,6 +12,7 @@ const steps = [
  • We check up to 1,000 starred GitHub Repositories against the registry. Choose which to watch. Email updates are optional.

    - -
    - - Page {{ syncProgress.page || '…' }} · {{ syncProgress.total }} Repositories checked · {{ syncProgress.matched }} match the registry - - - Last GitHub import - UTC + + +
    @@ -153,7 +149,7 @@ useSeoMeta({ title: 'Discover skills', robots: 'noindex' }) Checked the first 1,000 GitHub stars. Some starred Repositories were not checked.

    -
    + @@ -249,5 +245,10 @@ useSeoMeta({ title: 'Discover skills', robots: 'noindex' }) @click="watchSelected" />
    + diff --git a/test/unit/login-interactions.nuxt.test.ts b/test/unit/login-interactions.nuxt.test.ts index edf50159..0607cac9 100644 --- a/test/unit/login-interactions.nuxt.test.ts +++ b/test/unit/login-interactions.nuxt.test.ts @@ -2,6 +2,7 @@ import { mockNuxtImport, mountSuspended } from '@nuxt/test-utils/runtime' import { flushPromises } from '@vue/test-utils' import { beforeEach, describe, expect, it, vi } from 'vitest' import { defineComponent, h, ref } from 'vue' +import OnboardingSteps from '../../layers/identity/app/components/OnboardingSteps.vue' import { useAuth } from '../../layers/identity/app/composables/useAuth' import Discover from '../../layers/identity/app/pages/onboarding/discover.vue' @@ -28,6 +29,29 @@ beforeEach(() => { }) describe('onboarding watch save', () => { + it('announces import progress and completion', async () => { + let finishImport: (value: unknown) => void + mocks.fetch.mockImplementation(() => new Promise((resolve) => { + finishImport = resolve + })) + const wrapper = await mountSuspended(Discover, { global: { stubs: { OwnedSkillsPrompt: true } } }) + await wrapper.findAll('button').find(button => button.text().includes('Import stars again'))!.trigger('click') + expect(wrapper.get('[role="status"]').text()).toContain('Repositories checked') + finishImport!({ ok: true, page: 1, total: 1, matched: 1, hasMore: false, syncedAt: 1 }) + await flushPromises() + expect(wrapper.get('[role="status"]').text()).toContain('Last GitHub import') + wrapper.unmount() + }) + + it('announces a failed import while keeping the selected Repository', async () => { + mocks.fetch.mockRejectedValue(new Error('Could not import GitHub stars. Try again.')) + const wrapper = await mountSuspended(Discover, { global: { stubs: { OwnedSkillsPrompt: true } } }) + await wrapper.findAll('button').find(button => button.text().includes('Import stars again'))!.trigger('click') + await flushPromises() + expect(wrapper.get('[role="alert"]').text()).toContain('Could not import GitHub stars. Try again.') + expect((wrapper.get('input[type="checkbox"]').element as HTMLInputElement).checked).toBe(true) + wrapper.unmount() + }) it('keeps the selections and stays on discovery when the save fails', async () => { mocks.fetch.mockRejectedValue(new Error('offline')) const wrapper = await mountSuspended(Discover, { global: { stubs: { OwnedSkillsPrompt: true } } }) @@ -51,6 +75,14 @@ describe('onboarding watch save', () => { }) }) +it('identifies the current onboarding step beyond its color', async () => { + const wrapper = await mountSuspended(OnboardingSteps, { props: { step: 1 } }) + expect(wrapper.get('[aria-current="step"]').text()).toContain('Discover') + await wrapper.setProps({ step: 2 }) + expect(wrapper.get('[aria-current="step"]').text()).toContain('Email') + wrapper.unmount() +}) + describe('sign out', () => { async function mountAuth() { let auth: ReturnType From 90d4650ed7717d5ccf24ad1ae627fdf5f1e136e4 Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Mon, 12 Oct 2026 17:03:44 +1100 Subject: [PATCH 2/4] fix(identity): honor saved repository indexing setting --- COPY.md | 5 ++ .../app/components/OwnedSkillsPrompt.vue | 44 +++++++--- test/unit/owned-skills-prompt.nuxt.test.ts | 84 +++++++++++++++++++ 3 files changed, 122 insertions(+), 11 deletions(-) create mode 100644 test/unit/owned-skills-prompt.nuxt.test.ts diff --git a/COPY.md b/COPY.md index 841a5737..5d810367 100644 --- a/COPY.md +++ b/COPY.md @@ -74,6 +74,11 @@ These exact strings. Do not paraphrase them per page. | Nav: setup guides | All setup guides | guidance only: the link under the Developers menu cards. Links `/developers` | | Skillgen lead | Keep your package skill current. After each release tag, Skillgen opens a pull request that updates the Skill in your repository. You review it and decide what merges. | guidance only: the `/skillgen` line under the H1. Its first two sentences are the page's meta description. A "Make a skill" link above the H1 returns to `/make-skill` | | Skillgen opt-in | Skillgen opens a pull request after each release tag. It runs only on the repositories you turn on here. | guidance only: the intro of the Skillgen view on `/me?view=skillgen`. The `/skillgen` step 02 says the same in its own words: Skillgen never runs on a repository you did not turn on | +| Public repository settings | Your public repository settings | heading for `OwnedSkillsPrompt`. Describe the setting without claiming a scan added repositories. | +| Public repository settings: privacy notice | When indexing is on, skilld checks your public GitHub repositories for SKILL.md files. Each Skill it finds gets a public page. You can turn indexing off here. | explanation of public indexing, including when the saved setting is off. | +| Public repository settings: loading | Loading your repository settings. | status while the saved account setting loads. | +| Public repository settings: load error | Could not load your repository settings. Try again. | alert with a Retry action. Hide scan and privacy actions until the setting loads. | +| Public repository settings: save error | Could not turn off indexing. Indexing is still on. Try again. | alert after a failed privacy save. Preserve the saved setting and allow retry. | | Skillgen groups | Ready · Needs a change · Turn on all (n) · n of n on | guidance only: the two groups on `/me?view=skillgen`. Ready rows say "Updates on each new tag." when on and "Can update when you turn it on." when off. Needs a change rows show the refusal sentence from `skillgenRefusal` | | CLI H1 | The skilld CLI | guidance only: the `/cli` H1 and its OG title | | CLI line | Search, run, install, and keep Skills current. | guidance only: the `/cli` line under the H1. It is the CLI's own `--help` line, so change it in the CLI first | diff --git a/layers/identity/app/components/OwnedSkillsPrompt.vue b/layers/identity/app/components/OwnedSkillsPrompt.vue index 1adee645..69beb3d0 100644 --- a/layers/identity/app/components/OwnedSkillsPrompt.vue +++ b/layers/identity/app/components/OwnedSkillsPrompt.vue @@ -1,15 +1,10 @@ + + diff --git a/app/components/skill-card/_SkillCardIdentity.vue b/app/components/skill-card/_SkillCardIdentity.vue index 8023351f..d214378b 100644 --- a/app/components/skill-card/_SkillCardIdentity.vue +++ b/app/components/skill-card/_SkillCardIdentity.vue @@ -23,35 +23,18 @@ const emit = defineEmits<{ avatarError: [] }>() -const avatar = useTemplateRef('avatar') - -// An avatar can fail before this card hydrates, such as in a row that -// hydrates only once it scrolls into view. Its error event then fired with no -// listener, so the mount reads the image instead. -onMounted(() => { - const image = avatar.value - if (image?.complete && image.naturalWidth === 0) - emit('avatarError') -}) - /** The author names the owner, so the byline needs only the Repository after it. */ const where = computed(() => view.author ? view.repo : view.source)