fix(skills): show the new skill after creating it#5949
Conversation
The create page navigated to the new skill's detail route, but the unsaved-changes guard immediately undid it: `isDirty` was derived from `createSkill.isSuccess`, so on success the guard's effect fired `history.back()` to pop its sentinel entry and cancelled the navigation that had just run. The header stayed on "New skill" with the fields intact, and nothing confirmed the save. The guard now exposes `release()` to retire itself for the rest of the mount, and the create page calls it before navigating with `replace` so the sentinel entry is consumed rather than stacked. Also from a cleanup pass over the same surfaces: - `useCreateSkill` resolves the created row, so the page reads `created.id` instead of re-deriving it from the response list - seed the list cache with the upsert's authoritative list verbatim; the id-merge kept the previous ordering and appended the new skill last - treat placeholder data as loading in the detail page, which could otherwise flash "Skill not found." on a workspace switch - seed credential-detail drafts on id change rather than in a value-keyed effect, so a background refetch can't clobber an in-progress edit - toast on save and on delete failure, which were both silent
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryLow Risk Overview Skills UX and data: success toasts on create/save; delete failures surface a toast. UI cleanup: resource tile chrome is shared via Reviewed by Cursor Bugbot for commit 6aad77e. Configure here. |
Greptile SummaryThis PR fixes skill creation and deletion navigation while improving related cache, loading, notification, draft, and shared resource-row behavior.
Confidence Score: 5/5The PR appears safe to merge with no eligible blocking failures identified in this follow-up review. No blocking failures remain.
|
| Filename | Overview |
|---|---|
| apps/sim/app/workspace/[workspaceId]/components/credential-detail/hooks/use-unsaved-changes-guard.ts | Adds explicit release and rearm controls while tracking whether the history sentinel is consumed during a released interval. |
| apps/sim/app/workspace/[workspaceId]/skills/new/skill-create.tsx | Navigates directly to the returned created skill after releasing the guard and displays a success notification. |
| apps/sim/app/workspace/[workspaceId]/skills/[skillId]/skill-detail.tsx | Improves workspace-transition loading, save/delete notifications, and optimistic-delete navigation handling. |
| apps/sim/hooks/queries/skills.ts | Returns the created skill from the mutation, preserves authoritative response ordering, and narrows member-query invalidation. |
| apps/sim/app/workspace/[workspaceId]/settings/components/settings-resource-row/settings-resource-row.tsx | Reuses shared tile styling and centrally prevents trailing controls from shrinking. |
Sequence Diagram
sequenceDiagram
participant User
participant Page as Skill page
participant Guard as Unsaved-change guard
participant API as Skills API
participant Router
User->>Page: Create or delete skill
Page->>Guard: release()
Page->>API: Run mutation
alt Mutation succeeds
API-->>Page: Updated skill data
Page->>Router: replace(destination)
else Mutation fails after early release
API-->>Page: Error
Page->>Guard: rearm() when applicable
end
Reviews (4): Last reviewed commit: "fix(skills): track a sentinel consumed w..." | Re-trigger Greptile
…lifecycle Follow-ups from an audit of the skills and custom tools surfaces. Custom tools row, now matching the skills row exactly: - the trailing arrow could be squeezed by a long description; the shared row owns `flex-shrink-0` for its trailing slot, so the five callers that hand-rolled it (and the two that forgot) all get it - drop a redundant `cursor-pointer` — Tailwind v3's preflight already sets it on `button` - the tile chrome was defined twice, in ResourceTile and again in SettingsResourceRow, despite ResourceTile's doc claiming to be the single source. Both now share RESOURCE_TILE_BASE/RESOURCE_TILE_FILL, and ResourceTile's redundant wrapper div is gone - skills' row uses the text-sm/text-caption tokens instead of literal pixels; the added gap-[1px] offsets the 21px->20px line-height so the row stays 39px Guard and cache correctness: - the delete path released the guard after the await, but the delete is optimistic — the row leaves the cache first, so the form went clean mid-flight and popped the sentinel before the release landed. Release up front and rearm if the request fails - hold the loading frame across the whole optimistic delete instead of flashing "Skill not found." on the way out - custom tools had the same placeholder-data bug just fixed in skill detail, and worse: a deep-linked id could resolve against the workspace just left - keep cached rows the create response omits, so a concurrent create isn't dropped until the refetch lands
|
@cursor review |
release() suppressed the unload warning and the browser Back trap, but the in-app back link still keyed only on isDirty — so the Skills chip could open the unsaved-changes modal after a successful create, while the drafts were still populated and the navigation was already in flight.
|
@cursor review |
release() intentionally leaves the seeded history entry in place, but it also drops the popstate listener — so Back during the released window (an optimistic delete's round-trip) consumed that entry with nothing to record it. hasSentinelRef stayed true, and a failed delete's rearm() then skipped re-seeding, leaving the surface with no Back confirm despite unsaved edits. The released branch now keeps a bookkeeping-only popstate listener that clears the ref, so rearm() seeds a fresh entry when the old one is gone.
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 6aad77e. Configure here.
Summary
/skills/newwith the header still reading "New skill", the fields still filled, and no confirmation. The redirect was there but the unsaved-changes guard undid it:isDirtywas derived fromcreateSkill.isSuccess, so on success the guard's effect firedhistory.back()to pop its sentinel entry and cancelled therouter.pushthat had just run.release()to retire itself for the rest of the mount; the create page calls it before navigating withreplace, so the sentinel entry is consumed instead of stacked and Back doesn't land on a stale empty form.Rest is from a cleanup pass over the same surfaces:
useCreateSkillresolves the created row, so the page readscreated.idinstead of re-deriving it by name from the response list.POST /api/skillsreturns the samelistSkillsForUserpayload asGET, and the old id-merge preserved the previous ordering, briefly rendering the new skill at the bottom of acreatedAt desclist.useSkillsis workspace-scoped withkeepPreviousData, so a workspace switch served another workspace's list as success-with-no-match and flashed "Skill not found." Matches the existingintegration-skills-section.tsxprecedent.isDirtyand pop the sentinel mid-edit).replaceand retires the guard, so Back can't return to the deleted skill's URL.Type of Change
Testing
Typecheck,
bun run lint:check(19/19 packages),check:api-validation, and the skills test suite (22 tests) all pass. Not yet clicked through in a browser — the fix is reasoned from the history/effect ordering, so worth a manual pass on the deploy preview.Checklist