feat(customize): consolidate skills into one surface with Yours/Discover - #1089
Merged
Merged
Conversation
`/my-skills` was a top-level route reachable only by a link-out from
`/customize/skills`, so the same noun lived in two places with two different
answers to "what skills do I have?" — one page listed what you authored, the
other what you could turn on, and neither showed the whole set.
Both now live under `/customize/skills`, split by a `scope` query param:
- **Yours** (default) — skills you authored at any status, plus catalog skills
you have turned on. Dense rows, with edit/delete on the ones you own.
- **Discover** — catalog skills your roles grant that are still off. Browse
cards with a switch.
Turning a skill on is this platform's analogue of "installing" one: access is
RBAC and the only state a user owns is the enablement preference.
An `Add` menu replaces the old "New skill" button and the link-out, offering
*Upload skill* (`?import=1`) and *Create a skill*. It is gated on the same
404-from-`/skills/mine` signal that used to hide the whole `/my-skills` page.
No backend change. The page merges two endpoints that already existed:
`GET /skills/` (accessible + ACTIVE, with the preference) and
`GET /skills/mine` (the authored tier at every status). The merge is what keeps
a DRAFT skill visible to its author — widening `GET /skills/` to carry drafts
would surface them in the composer picker, which the runtime refuses to
activate. A draft therefore has no toggle at all rather than a dead one.
The authoring form moved to `/customize/skills/{new,:id/edit}` (git mv); the
three old `/my-skills` paths stay as redirects.
⚠️ `customize/skills/new` must stay declared above `customize/skills/:skillId`
or the parameterised route swallows it.
⚠️ `setScope` needs `relativeTo` on `router.navigate([])`, or the empty command
list resolves against the root and the query params are silently dropped. Found
in the browser, not by the tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
/my-skillsis gone. It was a top-level route reachable only by a link-out from/customize/skills, which put the same noun in two places with two different answers to "what skills do I have?" — one page listed what you authored, the other what you could turn on, and neither showed the whole set.Both now live under
/customize/skills, split by ascopequery param rather than by route:Turning a skill on is this platform's analogue of "installing" one — there is no install step, because access is RBAC (
resolve_accessible_skill_ids) and the only state a user owns is the enablement preference.An Add ▾ menu replaces the old "New skill" button and the link-out: Upload skill (
?import=1, which re-titles the form and leads with the SKILL.md import block) and Create a skill.No backend change
The page merges two endpoints that already existed:
GET /skills/(SkillService) — the picker feed: accessible and ACTIVE, with the enablement preference.GET /skills/mine(MySkillService) — the authored tier, at every status.GET /skills/to carry drafts was considered and rejected: it feeds the composer picker, so a draft would read as activatable in chat while the runtime's_apply_enabled_skills_filterrefuses it. Two reads on one page is the cheaper mistake.toggleableonSkillRowComponent), not a disabled one —toggleSkillreturns silently for a skill it never loaded, so the button would have been a control that does nothing.Routing
The authoring form moved to
/customize/skills/newand/customize/skills/:id/edit(git mv, so history follows it). The three old/my-skillspaths stay as redirects — they are in bookmarks, and the detail page linked to/my-skills/:id/editfor its whole life.customize/skills/newmust stay declared abovecustomize/skills/:skillId, or the parameterised route swallows it and the create form renders "skill not found" for a skill namednew. Same class of trap as the/settings/connectorsordering in step 2.setScopecallsrouter.navigate([], { relativeTo: this.route, ... })—relativeTois load-bearing. Without it the empty command list resolves against the root, the navigation lands on the same URL with the query params dropped, and the scope silently never changes. The browser caught this; the tests did not, because they drive thescopeinput directly.Cost
Nothing here reaches the model. Both reads are catalog data for display — the system prompt,
toolConfigand the cacheable prefix are untouched.Verification
npx ng test --watch=false— 2876 passed / 242 files, including a rewritten page spec and a newSkillRowComponentspec.npx tsc --noEmit -p tsconfig.app.jsonclean; production build compiles.localhost:4200, light and dark:/my-skills,/my-skills/:id/editredirects both land correctly/customize/skills/newis not swallowed by:skillId;?import=1re-titles to "Upload skill"Out of scope
Both were considered and deliberately left out — each is a separate feature with real backend work, not a view change:
.zip/.skillbundle upload. "Upload skill" still parses a singleSKILL.mdclient-side; a zipped bundle needs a backend unpack + per-file validation path.🤖 Generated with Claude Code