Skills: full detail page with diff-confirmed saves - #175
Merged
Merged
Conversation
Covers the line-diff utility (edits, insertions, removals, line numbering, empty sides), the skill detail page's diff-confirmed save (Save… opens the review with the diff and writes nothing, Keep editing keeps the edit, Confirm & save publishes), the version list with compare and restore, and the new read of a skill at one commit. The roster and route suites move to the roster-only contract: /skills lists, and a single skill lives at its own route. Claude-Session: https://claude.ai/code/session_01Shhie5zM8L54bLHq5gFQti
/skills/<name> is now a real page instead of a placeholder: the skill's content editor, its version list, and a diff view — no new storage, since a skill's git history already is its version store. A save is never silent. "Save…" in the top bar opens a review step showing the diff between the published version and the editor buffer, with "Confirm & save" and "Keep editing"; the commit happens only on confirm. The same renderer draws the comparison between any earlier version and the current one, read through a new GET /:name/versions/:commitSha that reads a skill at one commit without writing anything. The line diff itself is @corbits/text-diff, a dependency-free longest-common-subsequence script so an edit in the middle of a document reads as that one edit. The roster keeps only what a roster does: the inline skill panel and the create dialog's edit mode are gone, so there is one skill editor rather than two. Claude-Session: https://claude.ai/code/session_01Shhie5zM8L54bLHq5gFQti
Pins the diff's refusal to allocate for a wholly rewritten large document and its linear cost when the identical head and tail are trimmed, the collapse of long unchanged runs, and newline normalization. On the page: a save that lost the race is refused, re-diffed against the version that won, and never buries it; side-action failures stay local and never discard a dirty draft; a missing skill says so; and CRLF in the buffer neither reads as a change nor changes the bytes written. Claude-Session: https://claude.ai/code/session_01Shhie5zM8L54bLHq5gFQti
Two defects in the review flow, both fixed at the root. The diff allocated a dense longest-common-subsequence table over the whole document, so a 12k-line skill cost gigabytes. It now normalizes newlines, trims the identical head and tail before building anything, refuses the quadratic walk past a hard cap (1500 changed lines per side, 400k characters) with an honest "too large to show line by line" summary, and collapses long unchanged runs to a single row. The table itself is Int32Array-backed, so the capped worst case is about 9MB. The script is computed once and both the rows and the change summary are read off that one result. The confirmed diff was computed against the version loaded when the page opened, so a save could silently bury whoever published in between. A save now carries the version it was reviewed against; PUT /:name refuses a stale one with a 409, and the page re-reads, keeps the edit, and re-opens the review against what is actually published, saying why. Also: the buffer is newline-normalized so the bytes reviewed are the bytes written, and the description is saved exactly as reviewed rather than trimmed on the way out; side actions (visibility, restore, compare) report failures next to themselves instead of replacing the page, and never reset an unsaved edit; failures read through describeApiError rather than rendering a server message, with a distinct "no skill named …" state for a 404; the new version read validates its id at the route and 404s a commit that isn't in the skill's history rather than dating it to 1970; and the versions list stacks at 1100px, as DESIGN.md specifies. Claude-Session: https://claude.ai/code/session_01Shhie5zM8L54bLHq5gFQti
TheGreatAxios
force-pushed
the
cl-6416-skill-detail-page
branch
from
August 21, 2026 00:50
bc6d0bc to
8d1c056
Compare
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.
Linear: https://linear.app/abklabs/issue/CL-6416
/skills/<name>was a placeholder. It is now the real skill page, built onthe versioning that already shipped — a skill's git history is its version
store, so this adds no storage and no version table.
What's here
newline-normalized, so the bytes reviewed in the diff are the bytes saved.
showing the diff between the published version and the editor buffer, with
Confirm & save / Keep editing. The commit happens only on confirm.
was reviewed against;
PUT /:nametakes an optionalexpectedHeadShaandanswers 409 rather than writing when someone else published in between. The
page then re-reads, keeps the edit, and re-opens the review against what is
actually published, saying why — a confirmed diff can never bury the
version it was not shown.
(the existing route) and Compare, which diffs any earlier version
against the current one through the same renderer.
GET /:name/versions/:commitShareads a skill as it stood at onecommit, writing nothing; the version id is parsed at the route and a commit
outside the skill's history is a 404.
dialog's edit mode are gone; the roster lists, the page edits. Only the
skill entry leaves
detail-placeholders.tsx— the other placeholders stayfor their own tickets.
@corbits/text-diffBuild vs. buy: the MIT
diffpackage would have been a fine choice andnothing here is novel — I hand-rolled it because the tree carries no diff
dependency and this needs ~120 lines with no runtime deps, so a workspace
package is cheaper than a new third-party dependency in a public repo; if
this grows word-level or three-way diffing, swapping in
diffbehind thesame
diffTextsignature is the exit.It is bounded on purpose: newlines normalized, identical head and tail
trimmed before anything is allocated, then a hard cap (1500 changed lines per
side, 400k characters) that refuses the quadratic walk and returns an honest
"too large to show line by line" summary instead. Long unchanged runs collapse
to one row, the table is
Int32Array-backed (~9MB at the capped worst case),and the script is computed once — rows and the change summary read off the
same result.
Measured after the trim and cap (was 420MB at 6k lines / 1.3GB at 12k):
Tests
Diff-utility unit tests including the cap and the trim-shaped perf case; the
dialog flow (save opens the review with the diff, cancel keeps the edit,
confirm writes, a lost race is refused and re-diffed against the winner); the
version list from a stubbed registry; restore and compare hitting their
routes; CRLF neither reading as a change nor changing the bytes written; side
actions failing locally without discarding a dirty draft; and the
roster/route suites updated to the roster-only contract.
bun run checkgreen.Follow-up
DiffViewis workbench-local for now; promoting it to@corbits/react-uiper the DESIGN.md rule is CL-6449
(https://linear.app/abklabs/issue/CL-6449) rather than part of this PR.
The ticket's "pinned consumers behind latest" nudge is also not here — the
pinnedBypayload carries no pinned-version data, so there is nothing honestto render yet.
https://claude.ai/code/session_01Shhie5zM8L54bLHq5gFQti