Skip to content

Skills: full detail page with diff-confirmed saves - #175

Merged
TheGreatAxios merged 4 commits into
mainfrom
cl-6416-skill-detail-page
Aug 21, 2026
Merged

TheGreatAxios merged 4 commits into
mainfrom
cl-6416-skill-detail-page

Conversation

@TheGreatAxios

@TheGreatAxios TheGreatAxios commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Linear: https://linear.app/abklabs/issue/CL-6416

/skills/<name> was a placeholder. It is now the real skill page, built on
the 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

  • Content editor for a skill's description and body. The buffer is
    newline-normalized, so the bytes reviewed in the diff are the bytes saved.
  • Diff-confirmed saves. "Save…" in the top bar opens a review step
    showing the diff between the published version and the editor buffer, with
    Confirm & save / Keep editing. The commit happens only on confirm.
  • A head precondition on that confirm. The save carries the version it
    was reviewed against; PUT /:name takes an optional expectedHeadSha and
    answers 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.
  • Version list — note, author, and time per commit — with Restore
    (the existing route) and Compare, which diffs any earlier version
    against the current one through the same renderer.
  • GET /:name/versions/:commitSha reads a skill as it stood at one
    commit, writing nothing; the version id is parsed at the route and a commit
    outside the skill's history is a 404.
  • One editor, not two. The roster's inline skill panel and the create
    dialog's edit mode are gone; the roster lists, the page edits. Only the
    skill entry leaves detail-placeholders.tsx — the other placeholders stay
    for their own tickets.

@corbits/text-diff

Build vs. buy: the MIT diff package would have been a fine choice and
nothing 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 diff behind the
same diffText signature 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):

input result
6k lines, one line edited diffed, 2.3ms
12k lines, one line edited diffed, 1.1ms
20k lines, one line edited diffed, 1.3ms
6k / 12k lines wholly rewritten refused in <0.5ms, summary shown
1400 lines rewritten (just under the cap) diffed, 14ms

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 check green.

Follow-up

DiffView is workbench-local for now; promoting it to @corbits/react-ui
per 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
pinnedBy payload carries no pinned-version data, so there is nothing honest
to render yet.

https://claude.ai/code/session_01Shhie5zM8L54bLHq5gFQti

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
TheGreatAxios force-pushed the cl-6416-skill-detail-page branch from bc6d0bc to 8d1c056 Compare August 21, 2026 00:50
@TheGreatAxios
TheGreatAxios merged commit 259d294 into main Aug 21, 2026
@TheGreatAxios
TheGreatAxios deleted the cl-6416-skill-detail-page branch August 21, 2026 00:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant