Skip to content

Add hierarchical skills navigator - #504

Open
FBalint wants to merge 20 commits into
mainfrom
skills-hierarchical-list
Open

FBalint wants to merge 20 commits into
mainfrom
skills-hierarchical-list

Conversation

@FBalint

@FBalint FBalint commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Summary

This stacked PR adopts the shared hierarchical list from #484 in the Context Library and adds the Skills Navigator management workflows.

  • replace the existing Context Library landing content with a hierarchical collection, directory, and skill navigator
  • add create, edit, rename, delete, and cross-directory skill move workflows for writable collections
  • keep public and read-only collections visible while withholding mutation actions
  • add atomic server-side skill mutations and preserve skill metadata during moves and renames
  • add inline rename support to the shared styled list while preserving context-menu and drawer focus behavior
  • humanize skill labels and surface descriptions and relative update times
  • enable drag auto-scroll and await move completion so failed operations show a toast without a success announcement

Dependency

Testing

  • pnpm --filter @gadgets/ui test:run (58 tests)
  • pnpm --filter @gadgets/gatekeeper-context test:run (75 tests)
  • pnpm --filter @gadgets/ui build
  • pnpm --filter @gadgets/gatekeeper-context build
  • pnpm lint:check (existing warnings only)

Devin Review

@github-actions github-actions Bot added the gatekeeper Changes to a gatekeeper integration label Sep 15, 2026
@github-actions

Copy link
Copy Markdown

Preview: pr504-skills-hierar-0ac7d765

https://pr504-skills-hierar-0ac7d765-router.cloudflare-os-previews.workers.dev

Dashboard · deleted when this PR closes

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

This report is out of date. Scroll down for Devin Review's latest report on this PR.

Devin Review found 7 potential issues.

Devin Review

Comment on lines +473 to +476
return <SkillsNavigatorPage onSelectSkill={(collectionId, manifestPath) => {
setSelectedCollection(collectionId);
setSelectedDoc(manifestPath);
}} />;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Context-only collections become inaccessible

When a collection has no valid skill manifest, the navigator provides no route into its editor. onSelectSkill runs only for skill rows, leaving existing documents inaccessible in the management UI.

Learn more

The management page previously let users open every enabled collection. The replacement page enters CollectionEditor only after onSelectSkill supplies a manifest path. Collections containing ordinary context documents but no valid SKILL.md therefore have no selectable row that enters the editor. This affects read-only collections permanently because their users cannot add a placeholder skill.

Example: A public collection contains handbook.md and no skill manifest. It appears as an empty collection in the navigator, but clicking it only toggles expansion. The user cannot open handbook.md, although the previous collection row opened the editor.

Recommended fix: Add a collection-selection callback to SkillsNavigatorPage and SkillsNavigatorTree, and invoke it when a collection row is activated. Open CollectionEditor with a null selected path so CollectionOverview and the complete document tree remain reachable.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread packages/gatekeeper-context/src/context-collection.ts
Comment thread packages/gatekeeper-context/app/skills/SkillsNavigatorTree.tsx Outdated
Comment thread packages/gatekeeper-context/app/skills/addSkillNavigatorNode.ts
Comment thread packages/gatekeeper-context/app/skills/CreateCollectionDialog.tsx Outdated
Comment thread packages/gatekeeper-context/src/agent-skill.ts Outdated
Comment on lines +47 to +56
const loadedDocuments = documentResults.map(([id, documents]) => [
id,
documents ?? [],
] as const);
setData({
collections,
documents: new Map(loadedDocuments),
writableCollectionIds: new Set(writableIds.filter((id): id is string =>
id !== null && !failedCollectionIds.has(id))),
status: "ready",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔍 Partial load failures appear empty

A failed document request becomes an empty collection under ready status. Users see zero skills without an error or retry path.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@ask-bonk

ask-bonk Bot commented Sep 15, 2026

Copy link
Copy Markdown

@maxwellpeterson Bonk workflow failed. Check the logs for details.

View workflow run · To retry, trigger Bonk again.

@ask-bonk

ask-bonk Bot commented Sep 15, 2026

Copy link
Copy Markdown
  1. [P1] Collection deletion bypasses name confirmation. DeleteNavigatorNodeDialog.tsx:78 deletes an entire collection with one click, unlike the previous flow requiring its title. This risks accidental irreversible data loss.

  2. [P1] Root skill relocation breaks references. context-collection.ts:588 moves only a root SKILL.md; supporting files remain at root while agents resolve references under the new directory. Reject root moves and rename its metadata in place.

  3. [P2] Optimistic rename retains the deleted path. SkillsNavigatorTree.tsx:77 changes only the label while retaining the old manifestPath. Clicking before reload opens the removed manifest and leaves an empty editor.

  4. [P2] Rename completion steals focus. HierarchicalList.tsx:143 refocuses the row after blur, preventing Tab or mouse focus from moving elsewhere. Restore focus only for explicit Enter/Escape completion.

github run

devin-ai-integration[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@FBalint FBalint mentioned this pull request Sep 21, 2026
@FBalint
FBalint force-pushed the skills-hierarchical-list branch 2 times, most recently from b304988 to 729d77c Compare September 21, 2026 09:27

const skillCountLabel = (count: number) => `${count} ${count === 1 ? "skill" : "skills"}`;

const nodeId = (collectionId: string, node: SkillNavigatorNode) => node.type === "skill"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Keep a stable identity across skill moves

The shared list restores focus after an async move by looking up the moved item's id, but this ID includes manifestPath. Every successful cross-directory move changes that path, so when the refreshed navigator arrives the old row unmounts and HierarchicalListPrimitive cannot find the pending item; a keyboard move such as Alt+Left leaves focus on <body>. Preserve a stable item ID or explicitly map/refocus the destination path after the move.

expandAll={expandAll}
dragAndDrop={{
autoScroll: true,
canMoveTo: (item, parent) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Provide a keyboard path to every destination folder

canMoveSkillNavigatorNode rejects same-parent moves, but the shared keyboard model reaches a folder only with Alt+Right on the immediately preceding sibling. Because this navigator sorts directories before skills, most skills have another skill immediately before them and cannot enter any folder; even the first skill can enter only the final directory. Pointer users can drop on any directory, so keyboard-only users cannot perform the advertised cross-directory workflow. Add an accessible destination picker or a product-specific keyboard move mechanism rather than exposing only the generic positional shortcuts.

/>
<GitTokenManager
collectionId={collection.id}
branch={collection.content.branch}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Use the edited branch in mirror setup instructions

This passes the persisted branch while the adjacent field edits local branch state. If a user changes main to release, creates a token, follows the shown instructions, and then saves, the mirror is configured to push main while the collection subsequently refreshes release. Pass the current branch value (or require saving before token setup) so the one-time credential workflow cannot produce a mismatched mirror.

Add skill
</DropdownMenu.Item>
)}
<DropdownMenu.Item

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Wait for viewer capabilities before enabling collection creation

The Add menu is interactive while status === "loading", but viewerInfo remains its default { isAdmin: false, supportsGitCollections: false } until every collection's documents and access metadata finish loading. An admin can open and submit this dialog during that window and create an irreversible private web collection without being offered public or Git options. Disable collection creation until capabilities are loaded, or load/store those capabilities independently of the full navigator request.

@ask-bonk

ask-bonk Bot commented Sep 21, 2026

Copy link
Copy Markdown

Posted 4 actionable inline review comments.

github run

@FBalint
FBalint added this pull request to stack #538 September 21, 2026 11:01
Base automatically changed from hierarchical-list-component to main September 21, 2026 17:00
- Keep successful collections visible when one document load fails.
- Validate every generated destination path during skill moves.
- Validate rewritten manifest size during skill renames.
- Update optimistic rename state with new manifest/directory paths.
- Only restore focus after rename when completed via Enter/Escape.
- Refresh navigator again after Git collection setup closes.
- Clarify legacy directory compatibility in README.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

Devin Review

Comment on lines +499 to +500
let manifest = this.storage.documents.get(manifestPath);
if (!manifest) throw new Error(`Document not found: ${manifestPath}`);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Invalid manifests trigger subtree deletion

deleteContextSkill accepts any existing SKILL.md without validating it. A stale delete then removes the directory after the file stops being a skill.

Learn more

Skill deletion treats the containing directory as the skill bundle, so its broader deletion scope is safe only after the target is confirmed as a valid manifest. Creation, movement, and rename parse the manifest before changing a bundle, but deletion checks only the filename and existence. The collection editor can overwrite a manifest through putContextDocument, so a manifest displayed by an earlier navigator load can become invalid before the delete RPC arrives.

Example: The navigator loads release/SKILL.md as a skill with release/checklist.md. Another editor removes its frontmatter. Clicking the stale Delete action removes both files, although release/SKILL.md is now an ordinary invalid document.

Recommended fix: Decode and parse the stored manifest before deriving and deleting its directory, including the text-content requirement used by skill discovery. Reject invalid targets without deleting any documents.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gatekeeper Changes to a gatekeeper integration

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant